2009-08-12 8 views
7

Sto lavorando con un sistema legacy che ha un modello di dominio anemico .Rifacturazione della logica di dominio che accede ai repository in un sistema legacy

Il dominio ha le seguenti classi di entità: Car, CarType, CarComponent, CarComponentType.

Per ognuno di questi, c'è un repository separato. C'è anche un numero di servizi che accedono a questi repository e contengono fondamentalmente tutta la logica.

Ho bisogno di implementare un metodo che determina se un produttore può sostituire CarComponentType. La logica è la seguente: un componente può essere interrotto solo se non ci sono automobili esistenti con quel componente oggi.

Inizialmente, l'ho implementato in una classe di servizio.

public boolean canBeDiscontinued(CarComponentType carComponentType) { 
    List<Car> cars = carRepository.getCarsWithComponent(carComponentType); 
    return cars.isEmpty(); 
} 

Questo funziona, ma questa logica viene utilizzata da diversi altri punti del codice. Potrebbe crescere, e sembra qualcosa che potrebbe andare bene all'interno classe CarComponentType invece:

public boolean canBeDiscontinued() { 
    List<Car> cars = carRepository.getCarsWithComponent(this); 
    return cars.isEmpty(); 
} 

Tuttavia, non posso metterlo lì, dal momento che ha bisogno di accedere al repository (e se ho capito bene è un antipattern molto serio per le entità di essere a conoscenza del livello di accesso ai dati). Quando carico un tipo di componente, non posso caricare tutte le macchine di quel tipo poiché potrebbero essere migliaia di oggetti. Non stiamo utilizzando alcun ORM, quindi creare una raccolta lenta non solo è ingombrante ma anche molto soggetta a errori.

È più appropriato avere effettivamente questo metodo in una classe di servizio come ho fatto per la prima volta? Non è importante? C'è un'altra alternativa? Devo iniziare il refactoring da un altro punto di partenza?

C'è una domanda simile here. Ma la mia domanda riguarda Java, quindi non penso che la soluzione sia applicabile nel mio caso. Inoltre, mi dispiace in anticipo per l'utilizzo di auto e componenti come il mio modello di dominio. :)

risposta

3

Questo suona come un buon candidato per la Specification pattern

+0

In quale livello dovrei inserire l'implementazione della specifica? Infrastruttura? – Chris

+0

La specifica è parte integrante del livello Dominio, poiché i "concetti di dominio" sono implementati nelle specifiche. –

5

Frederik Gheysels risposta è buona, anche se forse un po 'corto. Elaborare: Nel tuo caso, si potrebbe iniziare da defininig un'interfaccia per la vostra specifica (scusa la mia sintassi C#):

public interface ICarSpecification 
{ 
    bool IsSatisfiedBy(CarComponentType carComponentType); 
} 

È possibile quindi creare un implmenetation di ICarSpecification che utilizza il repository. Qualcosa di simile a questo:

public class CanDiscontinueSpecification : ICarSpecification 
{ 
    private readonly CarRepository carRepository; 

    public CanDiscontinueSpecification(CarRepository carRepository) 
    { 
     this.carRepository = carRepository; 
    } 

    public bool IsSatisfiedBy(CarComponentType carComponentType) 
    { 
     return this.carRepository.GetCarsWithComponent(carComponentType).Any(); 
    } 
} 

Si poteva fermarsi lì, ma la cosa non mi piace in particolare il modello di specifica è che non è molto rilevabile.Una possibile soluzione potrebbe essere quella di iniettare lo Specification nella CarComponentType stesso:

public class CarComponentType 
{ 
    private readonly ICarSpecification discontinueSpec; 

    public CarComponentType(ICarSpecification discontinueSpec) 
    { 
     this.discontinueSpec = discontinueSpec; 
    } 

    public bool CanBeDiscontinued() 
    { 
     return this.discontinueSpec.IsSatisfiedBy(this); 
    } 
} 

In alternativa, se non vi piace portare in giro la specifica in ogni istanza della classe, è possibile utilizzare il metodo di iniezione, invece di Constructor iniezione :

public bool CanBeDiscontinued(ICarSpecification spec) 
{ 
    return spec.IsSatisfiedBy(this); 
} 

Tale metodo non aggiunge alcun valore in termini di implementazione, ma è più rilevabile.

+0

Grazie per aver chiarito. Se avessi più specifiche che potrebbero essere combinate, questo sembra appropriato. Nel mio caso, non sono sicuro. Invece di iniettare un repository in ogni entità, io inietto una specifica che * usa * un repository. E 'davvero molto meglio? – waxwing

+2

Beh, disaccoppia la tua classe dal repository ed è probabilmente più facile fare un test unitario passando in implementazioni di specifiche che fanno ciò che vuoi per un test. –

+0

Sì: ciò che chriskes ha appena detto :) Scherzi a parte, questo rende la Specifica solo un'altra Strategia, che può o meno dipendere da un Repository. Comunque, ho incluso l'alternativa Method Injection nel caso in cui davvero non ti piacesse avere una dipendenza universale dalla Specifica. –

1

Non penso che sia riduttivo dire che odio tanto i modelli di dominio anemico quanto il prossimo.

Tuttavia, dato che il sistema su cui si sta lavorando ha già stabilito il modello/modello di servizio anemico del dominio (anti), penso che l'introduzione di schemi aggiuntivi potrebbe essere per l'arresto del sistema quando viene eseguito in casi isolati, come credo tu abbia qui . Tale decisione dovrebbe essere presa dal team e dovrebbe avere chiari vantaggi.

Inoltre, IMHO, penso che i frammenti di codice originali siano più semplici delle soluzioni proposte in altre risposte (senza offesa Mark e Frederik, solo un'osservazione :-) e che la soluzione più complicata non porta alcun vantaggio - Voglio dire, la funzionalità è la stessa in entrambi i casi, ma in seguito utilizza più parti mobili.

In termini di come procedere, con un solo esempio è difficile dirlo. L'introduzione di un ORM (che hai menzionato non è attualmente utilizzato) potrebbe essere un modo per andare in quanto dovrebbe ridurre il codice nelle tue classi di servizio e avrebbe benefici evidenti, quindi sarei tentato di iniziare lì, quindi una volta che è in atto la revisione la situazione.

+0

Grazie. Mi ero quasi rassegnato a questa risposta da solo, ma mi stavo chiedendo per scopi accademici. Sento che * dovrebbe * essere una soluzione più intelligente che non vedo. – waxwing

3

Non penso che il "può essere interrotto" appartenga a nessuna di quelle classi. Chi è responsabile per determinare se una parte può essere interrotta? Non la macchina o il componente auto. Penso che tu fossi sulla buona strada con il tuo metodo iniziale implementato in un servizio. Forse avete bisogno di un CarInventoryManagementService che è responsabile per rispondere alle domande circa gli articoli di magazzino auto come:

carsUsingComponent(CarComponent comp) 
canComponentBeDiscontinued(CarComponent comp) 
etc 

Se si dispone di più punti del codice che hanno bisogno di porre domande relative inventario, come la tua "canBeDiscontinued", allora potrebbe avere senso creare un servizio solo con quella responsabilità.

+0

Questo è un po 'soggettivo. È molto più spesso di un caso limite se l'oggetto dominio deve essere "consapevole" di come viene utilizzato dai client. Forse hai ragione in questo caso. Ma diciamo che è una funzione diversa - qualcosa che si adatta indiscutibilmente a CarComponentType ma ha bisogno di dati non contenuti nell'oggetto stesso. Questo è quello che sono più interessante in. – waxwing

+0

Sto pensando al design guidato dalla responsabilità. Una macchina sarebbe responsabile per determinare se è ancora in produzione o il creatore della macchina? Una macchina potrebbe certamente sapere chi la produce, ma in realtà non ha alcun motivo per ritenere la responsabilità di prendere decisioni in merito al fatto che tutte le auto del suo tipo siano, ad esempio, a fine vita. Gli oggetti del dominio in genere dovrebbero rispondere solo a domande su se stessi. Le domande che si applicano a tutto un tipo di oggetto di dominio sono in genere la responsabilità di un Domain Object Service. Ovviamente non conosco questo software, ma è un principio generale. –

Problemi correlati