Come progettare le classi in php nel modello di repository?

0

Uso laravel con pattern di repository.

La mia strucuture del progetto è:

Ho una Entity class ( POPO ) che implementa un Interface e ha un repository class relativo ad esso.

Ad esempio per User Entity , ho:

App\Entities\User - Classe entità
App\Repos\User - Repository classe
App\Interfaces\User - interfaccia

La maggior parte delle azioni nel sistema di solito avvengono all'interno della classe di servizio.

Ad esempio: per un processo di pagamento, ho un servizio chiamato Pay . Per completare un pagamento, questi sono i metodi che potrei chiamare

$pay = new \App\Services\Pay;
$pay->asUser($user); //a user object
$pay->forItem($product); // a product class
$pay->pay(); //actually paid

Sono contento di questa implementazione e finora funziona benissimo.

Una cosa su cui posso vedere un miglioramento è la lunghezza del Entity Class . Ad esempio, la classe User ha circa 15 proprietà e tutte hanno una setter e una getter , (non voglio che le proprietà siano pubblicamente accessibili), il che rende il 30% dimethods e un po 'di% diactions and additional getters. Alcuni esempi potrebbero essere: getInactiveUsers , getActiveUsers , registerAsAdmin ecc. Ciò rende queste entità di classe enormi. Alcune entità attraversano 1000 linee.

Sono grandi all'inizio a causa di getters and setters Vorrei mantenere il logics or queries direttamente correlato agli utenti di questa classe.

Come posso mantenere queste lezioni brevi e concise?

    
posta developernaren 09.03.2018 - 12:52
fonte

2 risposte

2

La prima cosa che dovresti fare è dividere il tuo monolito in più moduli, un modulo per Contesto limitato . Questa è anche la decisione architettonica più importante che farai. Se lo fai correttamente, tutti i tuoi modelli (Aggregati) saranno più piccoli possibile. In PHP, i moduli possono essere implementati come namespace (directory di primo livello). Quindi, un esempio potrebbe essere:

App\Authentication
App\Authorization
App\Payment
App\Ordering

Quindi in ogni contesto limitato, avresti (cioè per App\Ordering ):

App\Ordering
    Domain
       Entities
          Buyer
          Order
          ...
       Repository
          BuyersRepository (interface)
          OrdersRepository (interface)
          ...
    Infrastructure
       Repository
          BuyersSqlRepository
          OrdersSqlRepository

Per ridurre la duplicazione del codice, le classi comuni potrebbero rimanere in uno spazio dei nomi di primo livello, ad esempio Framework . Esempi sono costruttori di query, fabbriche GUID ecc.

$pay = new \App\Services\Pay

Nei servizi DDD sono sempre stateless. Il tuo servizio sembra essere stato Inoltre, non è chiaro in che modo può essere utilizzato, quali campi sono opzionali e quali sono richiesti per il metodo pay . Dovresti farlo in questo modo:

$pay->pay($user, $product); //it is now clear that user and product are required

...the User class has around 15 properties and all of them have a setter and a getter, (I do not want properties to be publicly accessible)

Da qualsiasi punto di vista rilevante, avere public setter + getter e rendere la proprietà come public è la stessa cosa. Invece dovresti avere metodi che eseguono qualche azione specifica o restituire qualcosa, chiamato rispetto a Ubiquitous lingua , rispettando il principio CQS .

... getInactiveUsers, getActiveUsers ... That is making these entities class huge

Non riesco a vedere un'entità che avrebbe tali metodi. Questi sembrano essere parte di un'interfaccia di repository o di un servizio di dominio.

Se il tuo dominio è complesso, dovresti dare un'occhiata a CQRS , che aiuta ulteriormente a rendere i modelli più concisi dividendoli in scrittura / modelli di comando e modelli di lettura / interrogazione.

    
risposta data 09.03.2018 - 14:00
fonte
1

Ok, facciamo un passo indietro e proviamo a definire il problema prima di parlare della soluzione. È veramente importante capire perché stai combattendo con questo problema come mezzo per informarti quando iniziano a verificarsi problemi legati al futuro.

Mantenere le lezioni brevi e concise è uno dei principi fondamentali del DDD. Questo perché l'obiettivo di DDD è quello di modellare il comportamento di un sistema in modo tale da soddisfare i requisiti funzionali del proprio dominio aziendale. Gli oggetti di dominio dovrebbero esistere per svolgere un'attività, non semplicemente per contenere i dati. Scommetterei mele e arance perché l'unico motivo per cui hai un modello User è perché esiste una tabella [User] nel tuo archivio dati. Il problema qui è che i dati / attributi che contiene un oggetto sono raramente un buon punto di partenza quando si tenta di modellare i requisiti funzionali di un sistema. Oggetti di database come User , oltre a non implicare alcun comportamento, tendono ad essere troppo astratti e comprendono troppa conoscenza, quindi sono inadatti per il riutilizzo in un dominio particolare.

Un nome più adatto potrebbe essere Buyer o Shopper . Sebbene il tuo dominio Buyer possa mappare fisicamente la tua tabella [User] , sembra incredibilmente improbabile che il tuo processo di pagamento veramente richieda più di 15 attributi per effettuare una vendita (potrebbe essere sufficiente la id e PaymentMethod !). Questo modello sarebbe responsabile solo della gestione di un sottoinsieme dei campi nella tabella. Potresti avere un altro oggetto dominio denominato AccountManager che gestisce [User] email e password . Vedi dove vado? L'archivio dati è un dettaglio di implementazione che non deve riguardare il tuo dominio.

Quindi la ragione per cui perché stai incontrando questo problema ha meno a che fare con i dettagli di un singolo oggetto e altro con un difetto nel tuo modello di dominio.

Sarò d'accordo con @Coststantin Galbenu nel fatto che il tuo progetto trarrebbe beneficio dall'organizzazione del comportamento in moduli (directory) in base al Contesto limitato, ma non sarò d'accordo con un'ulteriore classificazione in sottodirectory che prendono il nome dai blocchi costitutivi del DDD. In realtà non aggiunge molto valore organizzativo, previene completamente gli spazi dei nomi secondari e accoppia la struttura della directory fisica al modello logico. I nomi delle classi stesse dovrebbero chiarire su quale "livello" dovrebbero cadere gli oggetti. Un'ulteriore nidificazione di spazi dei nomi può fornire ulteriori partizioni quando ha senso.

L'ultimo commento che vorrei fare riguarda il tuo servizio Pay . Mentre un servizio con quel nome può, in effetti, essere necessario in un sistema, quando stai spiegando il processo aziendale dell'acquisto di un prodotto a qualcun altro, cosa dici? Se dici "A User acquista Product ", mi aspetto un codice che dice questo: $user->purchase($product) . Sto armeggiando con le parole cercando di capire come posso descrivere in modo semplice e conciso il tuo approccio o riassumere il tuo codice. In DDD non è sufficiente che il tuo codice faccia faccia la cosa giusta, è importante che dica la cosa giusta.

Persino le urla di @Constantin Galbenu di un modello anemico in cui i processi sono separati in funzioni di alto livello applicate in cima al modello e i tuoi oggetti si trasformano in semplici sacchi di dati (sicuri per tipo) da passare come argomenti. Questo non è DDD! Questo è un approccio funzionale. Questo non lo rende sbagliato, solo diverso. Gli oggetti dovrebbero essere associati con un comportamento, non separati da esso.

    
risposta data 12.03.2018 - 21:16
fonte