refactor compose the repository port from concern signatures

Repository.S was one module type of thirty operations spanning accounts, the catalog, workouts, self-reports, application feedback, and imports. Every adapter and every test double had to implement all of it, and no consumer could ask for less than everything. The port is now six standalone concern signatures over a shared store type, and S composes them with include. Adapters and doubles are unchanged: S is the same set of operations. What changes is that a use case or a test double can now name one concern's signature instead of the whole, so a narrow consumer never carries operations it does not use.

Commit
079672ae6632558b329ffbaa4dd67c3502ea23f7
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/app/repository.ml
index 22bb9579..ac219e5d 100644..100644
@@ -37,7 +37,10 @@
37 37 provenance : Workout_import.provenance;
38 38 }
39 39
40 Removed: module type S = sig
40 Added: (* The concern signatures mirror repository.mli; the interface carries the
41 Added: documentation. *)
42 Added:
43 Added: module type Accounts = sig
41 44 type t
42 45
43 46 val create_trainee :
@@ -57,9 +60,18 @@
57 60
58 61 val update_credential :
59 62 t -> Trainee.id -> Trainee.credential -> Trainee.t option Lwt.t
63 Added: end
60 64
65 Added: module type Catalog = sig
66 Added: type t
67 Added:
61 68 val list_routines : t -> (routine_id * Prescription.Routine.t) list
62 69 val find_routine : t -> routine_id -> Prescription.Routine.t option
70 Added: end
71 Added:
72 Added: module type Workouts = sig
73 Added: type t
74 Added:
63 75 val active_routine : t -> Trainee.id -> routine_id option Lwt.t
64 76 val set_active_routine : t -> Trainee.id -> routine_id -> unit Lwt.t
65 77 val in_progress : t -> Trainee.id -> Evidence.Workout.t option Lwt.t
@@ -73,9 +85,18 @@
73 85 val delete_workout : t -> Trainee.id -> workout_id -> bool Lwt.t
74 86 val log : t -> Trainee.id -> Evidence.Log.t Lwt.t
75 87 val history : t -> Trainee.id -> record list Lwt.t
88 Added: end
89 Added:
90 Added: module type Self_reports = sig
91 Added: type t
92 Added:
76 93 val save_feedback : t -> Trainee.id -> Feedback.t -> unit Lwt.t
77 94 val feedback : t -> Trainee.id -> Feedback.t list Lwt.t
95 Added: end
78 96
97 Added: module type App_feedback = sig
98 Added: type t
99 Added:
79 100 val save_app_feedback :
80 101 t ->
81 102 Trainee.id ->
@@ -93,7 +114,11 @@
93 114
94 115 val delete_app_feedback :
95 116 t -> author:Trainee.id -> app_feedback_id -> bool Lwt.t
117 Added: end
96 118
119 Added: module type Imports = sig
120 Added: type t
121 Added:
97 122 val save_import_batch : t -> Trainee.id -> Workout_import.batch -> unit Lwt.t
98 123
99 124 val find_import_batch :
@@ -109,4 +134,15 @@
109 134
110 135 val replace_imported_record : t -> Trainee.id -> imported_record -> unit Lwt.t
111 136 val imported_records : t -> Trainee.id -> imported_record list Lwt.t
137 Added: end
138 Added:
139 Added: module type S = sig
140 Added: type t
141 Added:
142 Added: include Accounts with type t := t
143 Added: include Catalog with type t := t
144 Added: include Workouts with type t := t
145 Added: include Self_reports with type t := t
146 Added: include App_feedback with type t := t
147 Added: include Imports with type t := t
112 148 end
lib/app/repository.mli
index 28913dcc..fd0ec117 100644..100644
@@ -57,12 +57,18 @@
57 57 so a re-promotion after an edit replaces the record rather than duplicating
58 58 it. *)
59 59
60 Removed: (** Effects run in Lwt: an adapter may talk to a database. *)
61 Removed: module type S = sig
62 Removed: type t
60 Added: (** {1 Concern signatures}
63 61
64 Removed: (** {2 Accounts} *)
62 Added: The port is split by concern, each a standalone module type over a shared
63 Added: store [t]. {!S} composes them into the whole repository an adapter
64 Added: implements. A use case or test double that needs only one concern can name
65 Added: that concern's signature instead of the whole, so a narrow consumer never
66 Added: carries operations it does not use. *)
65 67
68 Added: (** Accounts: trainee identity and credentials. *)
69 Added: module type Accounts = sig
70 Added: type t
71 Added:
66 72 val create_trainee :
67 73 t ->
68 74 username:Trainee.username ->
@@ -84,15 +90,20 @@
84 90 val update_credential :
85 91 t -> Trainee.id -> Trainee.credential -> Trainee.t option Lwt.t
86 92 (** Replace the account's password verifier. [None] if unknown. *)
93 Added: end
87 94
88 Removed: (** {2 Catalog}
95 Added: (** Catalog: the authored routines. Shared, not trainee-scoped. *)
96 Added: module type Catalog = sig
97 Added: type t
89 98
90 Removed: Shared, not trainee-scoped. *)
91 Removed:
92 99 val list_routines : t -> (routine_id * Prescription.Routine.t) list
93 100 val find_routine : t -> routine_id -> Prescription.Routine.t option
101 Added: end
94 102
95 Removed: (** {2 Per-trainee selection and workout in progress} *)
103 Added: (** Workouts: a trainee's routine selection, the workout in progress, and the
104 Added: saved history. *)
105 Added: module type Workouts = sig
106 Added: type t
96 107
97 108 val active_routine : t -> Trainee.id -> routine_id option Lwt.t
98 109 val set_active_routine : t -> Trainee.id -> routine_id -> unit Lwt.t
@@ -102,8 +113,6 @@
102 113 t -> Trainee.id -> Evidence.Workout.t option -> unit Lwt.t
103 114 (** [None] clears the slot. *)
104 115
105 Removed: (** {2 History} *)
106 Removed:
107 116 val finish_workout : t -> Trainee.id -> Evidence.Workout.t -> record Lwt.t
108 117 (** Store a finished workout and clear the in-progress slot as one unit. An
109 118 adapter with transactions performs both in a single transaction, so a
@@ -120,12 +129,15 @@
120 129
121 130 val log : t -> Trainee.id -> Evidence.Log.t Lwt.t
122 131 (** Canonical workouts logged through hito. Promoted import observations are
123 Removed: stored separately in {!imported_records}. *)
132 Added: stored separately in {!Imports.imported_records}. *)
124 133
125 134 val history : t -> Trainee.id -> record list Lwt.t
126 135 (** Most recent first. *)
136 Added: end
127 137
128 Removed: (** {2 Subjective feedback} *)
138 Added: (** Self-reports: the trainee's subjective feedback. *)
139 Added: module type Self_reports = sig
140 Added: type t
129 141
130 142 val save_feedback : t -> Trainee.id -> Feedback.t -> unit Lwt.t
131 143 (** Store a subjective feedback report. Feedback is a standalone observation,
@@ -133,7 +145,12 @@
133 145
134 146 val feedback : t -> Trainee.id -> Feedback.t list Lwt.t
135 147 (** Stored feedback reports, most recent first. *)
148 Added: end
136 149
150 Added: (** Application feedback: freeform reports about the app, ranked by vote. *)
151 Added: module type App_feedback = sig
152 Added: type t
153 Added:
137 154 val save_app_feedback :
138 155 t ->
139 156 Trainee.id ->
@@ -156,14 +173,16 @@
156 173 val delete_app_feedback :
157 174 t -> author:Trainee.id -> app_feedback_id -> bool Lwt.t
158 175 (** Remove a report and its votes only when [author] owns it. *)
176 Added: end
159 177
160 Removed: (** {2 Workout import}
178 Added: (** Workout import.
161 179
162 Removed: A provisional import batch and the canonical records promoted from it have
163 Removed: independent lifecycles. A batch is stored so cleaning and promotion
164 Removed: survive a reconnect, and may be deleted once it is no longer needed. A
165 Removed: promoted record is canonical evidence and is never removed by deleting its
166 Removed: batch. *)
180 Added: A provisional import batch and the canonical records promoted from it have
181 Added: independent lifecycles. A batch is stored so cleaning and promotion survive
182 Added: a reconnect, and may be deleted once it is no longer needed. A promoted
183 Added: record is canonical evidence and is never removed by deleting its batch. *)
184 Added: module type Imports = sig
185 Added: type t
167 186
168 187 val save_import_batch : t -> Trainee.id -> Workout_import.batch -> unit Lwt.t
169 188 (** Store or replace a provisional import batch by its identity, including its
@@ -194,4 +213,19 @@
194 213
195 214 val imported_records : t -> Trainee.id -> imported_record list Lwt.t
196 215 (** Every promoted record for the trainee, most recent [ended_at] first. *)
216 Added: end
217 Added:
218 Added: (** {1 The whole port}
219 Added:
220 Added: Effects run in Lwt: an adapter may talk to a database. Every concern shares
221 Added: the one store [t]. *)
222 Added: module type S = sig
223 Added: type t
224 Added:
225 Added: include Accounts with type t := t
226 Added: include Catalog with type t := t
227 Added: include Workouts with type t := t
228 Added: include Self_reports with type t := t
229 Added: include App_feedback with type t := t
230 Added: include Imports with type t := t
197 231 end