[OCaml] High Intensity Training Online
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.
Changed files
lib/app/repository.ml
@@ -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
@@ -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