Aggiornamento degli oggetti nidificati in DDD Aggrega con l'esempio: delega o accesso diretto dalla radice?

7

Il problema del dominio di esempio

EsisteunsistemaditracciamentodellepresenzedeglistudentichetienetracciadellepresenzedeglistudentidiExerciseGroups.

  • Courseèuncomponentedilivellosuperiore,AR,descriveun'informazionegenericasulcorsodiapprendimento(nome,descrizione)
  • OgniCoursehaExerciseGroupschedescriveigruppiincuiglistudentipossonoesercitarsi.ExerciseGroupcontieneilnomedelgruppoel'elencodeglistudentiedeitutoriscritti.ExerciseGroupnonhasensoaldifuoridiCourse(daldominio),quindièsottoCourseAR.
  • OgniExerciseGroupcontieneunelencodiSessionschedescriveoccorrenzediExerciseGroupaUserpuòpartecipare.Èdefinitoda(datetimeBegins,datetimeEndseunsetdiAttendanceoggetti).
  • 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/SessionoperazionimutevolifattesolodaCourseAR.

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

  1. Qual è la tua opinione generale sul modello di dominio scelto? Ha senso per te? Non è troppo grasso?
  2. 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 chiedere Session per creare Attendance :

    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.

  1. Come affrontare gli oggetti nidificati profondi in termini di complessità del metodo AR? Diciamo che apparirà un altro oggetto btw Session e Attendance - Meeting , quindi il metodo in Course potrebbe diventare addExerciseGroupSessionMeetingAttendance(ExerciseGroup gId, SessionId sId, MeetingId mId, AttendanceParams.. o) che è estremamente lungo per me e poco chiaro in termini di (2)
  2. Dovrebbe addAREntity(EntityParam1,EntityParam1N) restituire EntityId o è valido restituire l'intero Entity (se si può essere certi che non è modificabile al di fuori del dominio, ad esempio tutti i metodi mutabili sono protected )? Il problema con la restituzione di solo EntityId è 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.

  1. 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

  1. 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;
       }
    }
    
  2. 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 )

posta ovnia 05.12.2017 - 09:49
fonte

1 risposta

2

Ruoterei il modello a testa in giù per rendere i tuoi più interessanti / comportamentali i tuoi AR.

In base alle informazioni che hai fornito, questo è quello che farei:

  • Il corso è un'entità di riferimento (non una AR)
  • L'utente è un'entità di riferimento
  • Il gruppo di esercizi è un'entità di riferimento (può anche registrare un collegamento tra corso e utente)
  • Sessione e / o partecipazione sono gli unici concetti con un comportamento potenzialmente interessante
    • Tutte le altre entità sono interessanti solo per la loro interazione all'interno di una sessione o presenza

La presenza conterrebbe riferimenti (ID) di Sessione e Utente e forse altre statistiche. Puoi scoprire quale corso hanno frequentato gli utenti seguendo la sessione - > gruppo di esercizi - > rapporto del corso.

Domanda sul dominio: è necessario che gli utenti frequentino solo le sessioni per le quali si trovavano nel gruppo di esercizi? Le persone presenti nella sessione allontanerebbero una persona che si presentava, anche se non erano nel gruppo di esercizi?

Potresti dire che alcune modifiche alla configurazione come l'unione e l'uscita dai gruppi potrebbero essere comportamenti interessanti. Dipende da quali sono gli obiettivi.

Ricorda inoltre che il tuo modello di dominio (sul lato scrittura) non deve essere uguale al modello letto. Provare a mescolare query e domini può farti girare in cerchio. Per quanto possibile, lascia che il dominio si occupi solo di modellare il problema aziendale. Se necessario, copia i dati del dominio in altri moduli più facili da consultare. Per esempio. trigger di database, job ETL, codice che viene eseguito su save, ecc.

Molte delle domande che hai postato non si applicano in realtà a ciò che ho detto sopra, quindi non ho risposto direttamente. Sentiti libero di porre eventuali altre domande sulla mia risposta nei commenti.

    
risposta data 05.12.2017 - 23:52
fonte