L'architettura pulita di zio Bob: un'entità / classe di modello per ogni livello?


44

SFONDO :

Sto cercando di utilizzare l'architettura pulita di zio Bob nella mia app Android. Ho studiato molti progetti open source che stanno cercando di mostrare il modo giusto di farlo e ho trovato un'implementazione interessante basata su RxAndroid.

COSA AVVISO:

In ogni livello (presentazione, dominio e dati), esiste una classe modello per la stessa entità (UML parlante). Inoltre, ci sono classi di mapper che si occupano della trasformazione dell'oggetto ogni volta che i dati attraversano i confini (da un livello all'altro).

DOMANDA:

È necessario disporre di classi di modelli in ogni livello quando so che finiranno tutti con gli stessi attributi se sono necessarie tutte le operazioni CRUD? Oppure, è una regola o una buona pratica quando si utilizza l'architettura pulita?

Risposte:


52

Secondo me, non è assolutamente così. Ed è una violazione di DRY.

L'idea è che l'oggetto entità / dominio nel mezzo sia modellato per rappresentare il dominio nel modo più buono e conveniente possibile. È al centro di tutto e tutto può dipendere da esso poiché il dominio stesso non cambia la maggior parte delle volte.

Se il tuo database esterno può archiviare direttamente quegli oggetti, mapparli in un altro formato per separare i livelli non è solo inutile, ma crea duplicati del modello e questa non è l'intenzione.

Per cominciare, l'architettura pulita è stata realizzata pensando a un ambiente / scenario tipico diverso. Applicazioni server aziendali con livelli esterni behemoth che richiedono i propri tipi di oggetti speciali. Ad esempio database che producono SQLRowoggetti e necessitano SQLTransactionsin cambio di aggiornamenti di elementi. Se dovessi usare quelli al centro, dovresti violare la direzione della dipendenza perché il tuo core dipenderà dal database.

Con ORM leggeri che caricano e archiviano oggetti entità, non è così. Fanno la mappatura tra il loro interno SQLRowe il tuo dominio. Anche se hai bisogno di inserire @Entitiyun'annotazione dell'ORM nel tuo oggetto dominio, direi che questo non stabilisce una "menzione" del livello esterno. Poiché le annotazioni sono solo metadati, nessun codice che non le sta cercando specificamente le vedrà. E, cosa ancora più importante, nulla deve cambiare se li rimuovi o li sostituisci con un'annotazione di un database diverso.

Al contrario, se cambi il tuo dominio e hai creato tutti quei mappatori, devi cambiare molto.


Emendamento: sopra è un po 'semplificato e potrebbe anche essere sbagliato. Perché c'è una parte in un'architettura pulita che vuole che tu crei una rappresentazione per livello. Ma questo deve essere visto nel contesto dell'applicazione.

Vale a dire quanto segue qui https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

L'importante è che strutture di dati isolate, semplici, passino attraverso i confini. Non vogliamo imbrogliare e passare le entità o le righe del database. Non vogliamo che le strutture di dati abbiano alcun tipo di dipendenza che viola la regola di dipendenza.

Passare entità dal centro verso i livelli esterni non viola la regola di dipendenza, eppure vengono menzionate. Ma questo ha una ragione nel contesto dell'applicazione prevista. Il passaggio di entità in giro sposterebbe la logica dell'applicazione verso l'esterno. Gli strati esterni dovrebbero sapere come interpretare gli oggetti interni, dovrebbero effettivamente fare ciò che dovrebbero fare gli strati interni come lo strato "use case".

Oltre a ciò, disaccoppia i livelli in modo che le modifiche al nucleo non richiedano necessariamente cambiamenti nei livelli esterni (vedere il commento di SteveCallender). In quel contesto, è facile vedere come gli oggetti dovrebbero rappresentare in modo specifico lo scopo per cui sono utilizzati. Inoltre, i livelli dovrebbero dialogare tra loro in termini di oggetti realizzati appositamente allo scopo di questa comunicazione. Questo può anche significare che ci sono 3 rappresentazioni, 1 in ogni strato, 1 per il trasporto tra i livelli.

E c'è https://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html che si rivolge sopra:

Altre persone si sono preoccupate che il risultato netto del mio consiglio sarebbe stato un sacco di codice duplicato e molta copia automatica dei dati da una struttura di dati a un'altra attraverso i livelli del sistema. Certamente non lo voglio neanche io; e nulla di ciò che ho suggerito porterebbe inevitabilmente alla ripetizione delle strutture di dati e a una sovraordinata copia sul campo.

Che IMO implica che la semplice copia 1: 1 di oggetti è un odore nell'architettura perché non stai effettivamente usando i livelli e / o le astrazioni corretti.

Spiega in seguito come immagina tutta la "copia"

Separare l'interfaccia utente dalle regole aziendali passando semplici strutture di dati tra i due. Non comunichi ai tuoi controller nulla sulle regole aziendali. Al contrario, i controller scompattano l'oggetto HttpRequest in una semplice struttura di dati vanilla, quindi passano quella struttura di dati a un oggetto interattore che implementa il caso d'uso invocando oggetti business. L'interattatore quindi raccoglie i dati di risposta in un'altra struttura di dati vanilla e li restituisce all'interfaccia utente. Le viste non sono a conoscenza degli oggetti business. Guardano solo in quella struttura di dati e presentano la risposta.

In questa applicazione, c'è una grande differenza tra le rappresentazioni. I dati che fluiscono non sono solo le entità. E questo garantisce e richiede classi diverse.

Tuttavia, applicato a una semplice applicazione Android come un visualizzatore di foto in cui l' Photoentità ha circa 0 regole aziendali e il "caso d'uso" che le tratta è quasi inesistente e in realtà è più preoccupato per la memorizzazione nella cache e il download (tale processo dovrebbe essere IMO rappresentato in modo più esplicito), il punto di rendere rappresentazioni separate di una foto inizia a svanire. Ho persino la sensazione che la foto stessa sia l'oggetto di trasferimento dei dati mentre manca il vero livello di business-logic-core.

C'è una differenza tra "separare l'interfaccia utente dalle regole aziendali passando semplici strutture di dati tra i due" e "quando si desidera visualizzare una foto rinominandola 3 volte lungo la strada" .

Oltre a ciò, il punto in cui vedo che quelle applicazioni demo non riescono a rappresentare l'architettura pulita è che aggiungono un'enfasi enorme sulla separazione dei livelli per motivi di separazione dei livelli, ma nascondono effettivamente ciò che l'applicazione fa. Ciò è in contrasto con quanto detto in https://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html - vale a dire che

l'architettura di un'applicazione software urla sui casi d'uso dell'applicazione

Non vedo quell'enfasi sulla separazione dei livelli nell'architettura pulita. Riguarda la direzione delle dipendenze e si concentra sulla rappresentazione del nucleo dell'applicazione - entità e casi d'uso - in java idealmente semplice senza dipendenze verso l'esterno. Non si tratta tanto di dipendenze verso quel nucleo.

Pertanto, se l'applicazione ha effettivamente un core che rappresenta le regole aziendali e i casi d'uso e / o persone diverse lavorano su livelli diversi, separarli nel modo previsto. Se invece stai scrivendo una semplice app tutta per te, non esagerare. 2 strati con limiti fluidi possono essere più che sufficienti. E i livelli possono essere aggiunti anche in seguito.


1
@RamiJemli Idealmente, le entità sono le stesse in tutte le applicazioni. Questa è la differenza tra "regole di business a livello aziendale" e "regole di business dell'applicazione" (a volte business vs logica dell'applicazione). Il nucleo è una rappresentazione molto astratta delle tue entità che è abbastanza generica da poterla usare ovunque. Immagina una banca che ha molte applicazioni, una per l'assistenza clienti, una in esecuzione sui bancomat, una come interfaccia utente per i clienti stessi. Tutti quelli potrebbero usare lo stesso BankAccountma con regole specifiche dell'applicazione cosa puoi fare con quell'account.

4
Penso che un punto importante nell'architettura pulita sia che usando il livello dell'adattatore di interfaccia per convertire (o come dici tu mappa) tra la rappresentazione dei diversi livelli dell'entità riduci la dipendenza a detta entità. In caso di cambiamenti nei livelli Usecase o Entity (si spera improbabile, ma poiché i requisiti cambieranno questi livelli), l'impatto della modifica è contenuto nel livello dell'adattatore. Se scegli di utilizzare la stessa rappresentazione dell'entità per tutta la tua architettura, l'impatto di questo cambiamento sarebbe molto maggiore.
SteveCallender,

1
@RamiJemli è utile usare framework che semplificano la vita, il punto è che dovresti stare attento quando la tua architettura si basa su di essi e inizi a metterli al centro di tutto. Ecco anche un articolo sul blog RxJava.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html - non sta dicendo che non dovresti usarlo. È più simile: l'ho visto, sarà diverso in un anno e quando la tua applicazione è ancora in giro sei bloccato con esso. Rendilo un dettaglio e fai le cose più importanti nella semplice vecchia Java mentre applichi i semplici vecchi principi SOLIDI.
zapl,

1
@zapl Ti senti allo stesso modo su un livello di servizio web? In altre parole, inseriresti @SerializedNamele annotazioni Gson in un modello di dominio? O creeresti un nuovo oggetto responsabile della mappatura della risposta web al modello di dominio?
tir38

2
@ tir38 La separazione in sé non offre il vantaggio, è il costo dei futuri cambiamenti che ne deriva. => Dipende dall'applicazione. 1) ti costa tempo per creare e mantenere la fase aggiunta che si trasforma tra diverse rappresentazioni. Ad esempio aggiungere un campo al dominio e dimenticare di aggiungerlo da qualche altra parte non è inaudito. Non può succedere con l'approccio semplice. 2) Costa passare a una configurazione più complessa in seguito nel caso risultasse necessario. Aggiungere livelli non è facile, quindi è più facile giustificare più livelli non necessari immediatamente in applicazioni di grandi dimensioni
zapl

7

In realtà hai capito bene. E non vi è alcuna violazione di DRY perché accetti SRP.

Ad esempio: hai un metodo di business createX (nome stringa), quindi potresti avere un metodo createX (nome stringa) nel livello DAO, chiamato all'interno del metodo aziendale. Possono avere la stessa firma e forse c'è solo una delegazione ma hanno scopi diversi. Puoi anche avere un createX (nome stringa) su UseCase. Anche allora non è ridondante. Ciò che intendo con questo è: le stesse firme non significano la stessa semantica. Scegli altri nomi affinché la semantica sia chiara. La denominazione stessa non influisce affatto su SRP.

UseCase è responsabile della logica specifica dell'applicazione, l'oggetto business è responsabile della logica indipendente dall'applicazione e il DAO è responsabile della memorizzazione.

A causa della diversa semantica, tutti i livelli possono avere il proprio modello di rappresentazione e comunicazione. Spesso vedi "entità" come "oggetti business" e spesso non vedi la necessità di separarle. Ma in progetti "enormi" si dovrebbe fare uno sforzo per separare adeguatamente gli strati. Maggiore è il progetto, aumenta la possibilità che siano necessarie le diverse semantiche rappresentate in diversi strati e classi.

Puoi pensare a diversi aspetti dello stesso semantico. Un oggetto utente deve essere visualizzato sullo schermo, ha alcune regole di coerenza interne e deve essere memorizzato da qualche parte. Ogni aspetto dovrebbe essere rappresentato in una classe diversa (SRP). Creare i mapper può essere una seccatura, quindi nella maggior parte dei progetti in cui ho lavorato questi aspetti sono fusi in una classe. Questa è chiaramente una violazione di SRP ma a nessuno importa davvero.

Chiamo l'applicazione di architettura pulita e SOLID "non socialmente accettabile". Lavorerei con esso se mi è permesso. Al momento non mi è permesso farlo. Aspetto il momento in cui dobbiamo pensare di prendere sul serio SOLID.


Penso che nessun metodo nel livello dati dovrebbe avere la stessa firma di qualsiasi metodo nel livello dominio. Nel livello Dominio, usi convenzioni di denominazione relative al business come iscrizione o login e nel livello Dati, usi il salvataggio (se modello DAO) o aggiungi (se Repository perché questo modello usa Collection come metafora). Infine, non sto parlando di entità (Dati) e modello (Dominio), sottolineo l'inutilità di UserModel e del suo Mapper (livello di presentazione). Puoi chiamare la classe User del dominio all'interno della presentazione e questo non viola la regola di dipendenza.
Rami Jemli,

Concordo con Rami, il mapper non è necessario perché è possibile eseguire il mapping direttamente nell'implementazione dell'interattatore.
Christopher Perry,

5

No, non è necessario creare classi di modelli in ogni livello.

Entity ( DATA_LAYER) - è una rappresentazione completa o parziale dell'oggetto Database.DATA_LAYER

Mapper ( DOMAIN_LAYER) - in realtà è una classe che converte Entity in ModelClass, che verrà utilizzata suDOMAIN_LAYER

Dai un'occhiata: https://github.com/lifedemons/photoviewer


1
Ovviamente, non sono contrario alle entità nel livello dati, ma, nel tuo esempio, la classe PhotoModel nel livello presentazione presenta gli stessi attributi della classe Photo nel livello dominio. È tecnicamente la stessa classe. è necessario?

Penso che qualcosa sia spento nel tuo esempio in quanto il livello del dominio non dovrebbe dipendere da altri livelli poiché nel tuo esempio i tuoi mappatori dipendono dalle entità nel tuo livello dati quali IMO, dovrebbe essere viceversa
navid_gh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.