Il problema del dominio di esempio
EsisteunsistemaditracciamentodellepresenzedeglistudentichetienetracciadellepresenzedeglistudentidiExerciseGroup
s.
Course
èuncomponentedilivellosuperiore,AR,descriveun'informazionegenericasulcorsodiapprendimento(nome,descrizione)- Ogni
Course
haExerciseGroup
schedescriveigruppiincuiglistudentipossonoesercitarsi.ExerciseGroup
contieneilnomedelgruppoel'elencodeglistudentiedeitutoriscritti.ExerciseGroup
nonhasensoaldifuoridiCourse
(daldominio),quindièsottoCourse
AR. - Ogni
ExerciseGroup
contieneunelencodiSessions
chedescriveoccorrenzediExerciseGroup
aUser
puòpartecipare.Èdefinitoda(datetimeBegins,datetimeEndseunsetdiAttendance
oggetti). Attendance
èunrecordVOchecontieneinformazionisullepresenzedeglistudenti.ContieneunriferimentoalUser
(viaId)ealcunidatistatistici.
Qualiapproccihousatofinoadora:
1.RiferimentoperidentitàaldifuoridelAR.classGroup{//...Set<UserId>studentIds;Set<UserId>tutorIds;}
2.Riferimentidirettiall'internodell'AR.classCourse{//....List<Group>groups;}
3.ExerciseGroup
/Session
operazionimutevolifattesolodaCourse
AR.Seguendol'ideache:
aggregaterootsexisttoprotectthedatawithinthem...[youmustnot]teartheaggregaterootsinternalstructuresanddooperationsontheinternalsdirectlywithoutincorporatingtheaggregateroot,[since]theaggregaterootcannolongervalidatetheoperationisvalid.
@david-packer(
https://softwareengineering.stackexchange.com/a/354667/169341)
Ho le seguenti operazioni mutabili in AR:
class Course {
ExerciseGroup addExerciseGroup(String exGroupName...);
void removeExerciseGroup(ExerciseGroupId exGroupId);
void attendExerciseGroupSession(ExerciseGroupId exGroupId, SessionId sessionId, UserId userId...);
// etc
}
Pur avendo ancora accesso in sola lettura alle entità sottostanti:
class Course {
List<ExerciseGroup> exerciseGroups() {
// save because all mutable operators in ExerciseGroup
// are 'protected' and available nobody but Course AR.
return unmodifiableList(this.exerciseGroups);
}
// etc
}
I problemi e le domande
- Qual è la tua opinione generale sul modello di dominio scelto? Ha senso per te? Non è troppo grasso?
-
Dovrei delegare le entità sottostanti all'AR per mutare i suoi figli o mettere l'intera logica nell'AR? Ad esempio, posso delegare
ExerciseGroup
a chiedereSession
per creareAttendance
:class Course { void attendExerciseGroupSession(ExerciseGroupId gId, SessionId sId, UserId uId) { this.eGroups.getById(gId).attendSession(sId, uId); } } class ExerciseGroup { void attendSession(SessionId sessionId, UserId userId) { this.sessions.getById(sessionId).attend(userId); } } class Session { void attend(UserId userId) { this.attendances.add(new Attendance(userId, LocalDateTime.now())); } }
Oppure posso creare presenze direttamente in
Course
AR.class Course { void attendExerciseGroupSession(ExerciseGroupId exGroupId, SessionId sessionId, UserId userId) { this.exerciseGroups.getById(exGroupId) .sessions().getById(sessionId) .attendances().add(new Attendance(userId, LocalDateTime.now())); } }
Mentre il primo approccio ha più senso per me, è diventato estremamente complicato non dimenticare di "propagare" alcuni metodi all'AR superiore. Una modifica in Session
(ad esempio, modifica di void in attend(.)
in Attendance
) richiede modifiche in tutte le classi di primo livello.
- Come affrontare gli oggetti nidificati profondi in termini di complessità del metodo AR? Diciamo che apparirà un altro oggetto btw
Session
eAttendance
-Meeting
, quindi il metodo inCourse
potrebbe diventareaddExerciseGroupSessionMeetingAttendance(ExerciseGroup gId, SessionId sId, MeetingId mId, AttendanceParams.. o)
che è estremamente lungo per me e poco chiaro in termini di (2) - Dovrebbe
addAREntity(EntityParam1,EntityParam1N)
restituireEntityId
o è valido restituire l'interoEntity
(se si può essere certi che non è modificabile al di fuori del dominio, ad esempio tutti i metodi mutabili sonoprotected
)? Il problema con la restituzione di soloEntityId
è che in genere è necessario l'oggetto while subito dopo aver creato l'oggetto.
Ad esempio, un endpoint POST /courses/{courseId}/groups/{groupId}/sessions/
molto probabilmente dovrebbe restituire un oggetto Session
completo (ad es. per ottenere tutti i dati autogenerati [id?] non inclusi nel DTO dati all'endpoint). Ricevere SessionId
da Course#addGroupSession(dto.param1(),..)
significa che ho bisogno di course.group(groupId).session(sessionId)
per soddisfare questo requisito.
- Sarà corretto restituire le entità nidificate agli utenti di AR se siamo sicuri che sono immutabili dall'esterno (ad esempio tutti i metodi mutabili sono protetti).
In altre parole, va bene fare course.getGroup(groupId).getSession()(sessionId)
dell'esempio precedente (supponendo .group(groupId)
e .session(sessionId)
restituire oggetti non modificabili), o è meglio avere course.getGroupSession(groupId, sessionId)
?
Grazie.
Un giorno dopo (aggiornamento)
Ho rivisitato il mio modello e l'ho trovato molto grasso, difficile da mantenere e progettato pensando a invarianti falsi che hanno causato errori di transazione. Ad esempio, nulla sulla creazione di un nuovo elemento di sessione dovrebbe interferire logicamente con la modifica di un corso.
Ciò che mi è venuto in mente dopo un'analisi transazionale più approfondita è il seguente modello (selezionabile):
La domanda che stavo usando per separare il contesto limite è: può / dovrebbe questo e quello nella stessa transazione? Quando partecipi a Session, dovrebbe essere fatto nella stessa transazione con Course [blocked]? Sicuramente non è ecc.
Per soddisfare la condizione che la sessione non debba essere al di fuori del gruppo, ho appena inserito GroupId nel controller della Session e spero in una coerenza finale:)
Domande rimaste
-
Sarà corretto restituire entità annidate da una AR se siamo sicuri che sono immutabili dall'esterno direttamente (ad esempio tutti i metodi mutabili sono protetti) [non da AR, il proprietario dell'entità]. In altre parole, è ok per fare questo:
class Course { List<Group> groups; List<Group> getGroups() {return Collections.unmodifiableList(groups);} public setGroupName(GroupId gi, String name) { this.findGroup(gi).setName(name); } } class Group { String name; // Protected so Course#getGroups users won't be able to mutate Group protected setName(String name) { this.name = name; } }
-
Dovrei delegare le entità sottostanti all'AR per mutare i suoi figli o mettere l'intera logica nell'AR? (vedi i dettagli sopra, # 2 in I problemi e le domande )