15

Modifica: Questo non è un conflitto a livello teorico ma un conflitto a livello di implementazione.Come posso risolvere il conflitto tra l'accoppiamento lento/l'iniezione di dipendenza e un modello di dominio avanzato?

Un altro Edit: Il problema non è avere modelli di dominio come di soli dati/DTOs contro ricchi, mappa oggetto più complesso, dove Ordine ha OrderItems e una certa logica calculateTotal. Il problema specifico è quando, per esempio, quell'Ordine deve prendere gli ultimi prezzi all'ingrosso dell'OrdineOtem da qualche servizio web in Cina (per esempio). Quindi hai un servizio Spring in esecuzione che consente chiamate a questo servizio PriceQuery in Cina. L'ordine ha calcolare TotalTotal che itera su ogni OrderItem, ottiene il prezzo più recente e lo aggiunge al totale.

Quindi, come faresti a garantire che ogni ordine abbia un riferimento a questo servizio di PriceQuery? Come lo si ripristinerà in caso di serializzazione, caricamento da DB e nuove istanziazioni? Questa è la mia domanda esatta.

Il modo più semplice sarebbe passare un riferimento al metodo calculateTotal, ma cosa succede se il proprio oggetto utilizza questo servizio internamente per tutta la sua durata? Cosa succede se è usato in 10 metodi? Diventa caotico passare i riferimenti in ogni momento.

Un altro modo sarebbe di spostare calcoli fuori dall'Ordine e in OrderService, ma che interrompe il design di OO e ci muoviamo verso il vecchio modo di "Script di transazione".

Original post:

Versione corta: oggetti di dominio Rich richiedono riferimenti a molti componenti, ma questi oggetti vengono persistevano o serializzato, così qualsiasi riferimento tengono ai componenti esterni (fagioli di primavera in questo caso : servizi, repository, qualsiasi cosa) sono transitori e vengono spazzati via. Devono essere reiniettati quando l'oggetto è de-serializzato o caricato dal DB, ma questo è estremamente brutto e non riesco a vedere un modo elegante per farlo.

Versione più lunga: Per un po 'di tempo ho praticato l'accoppiamento libero e il DI con l'aiuto di Spring. Mi ha aiutato molto a mantenere le cose gestibili e verificabili. Qualche tempo fa, tuttavia, ho letto Domain-Driven Design e alcuni Martin Fowler. Di conseguenza, ho cercato di convertire i miei modelli di dominio da semplici DTO (in genere rappresentazioni semplici di una riga di tabella, solo dati senza logica) in un modello di dominio più ricco.

Mentre il mio dominio cresce e assume nuove responsabilità, i miei oggetti dominio stanno iniziando a richiedere alcuni dei bean (servizi, repository, componenti) che ho nel mio contesto Spring. Questo è diventato rapidamente un incubo e una delle parti più difficili della conversione in un design di dominio ricco.

Fondamentalmente ci sono punti in cui sto iniettando manualmente un riferimento al contesto di applicazione nel mio dominio:

  • quando l'oggetto viene caricato dal repository o altro ente responsabile in quanto i riferimenti dei componenti sono transitori e, ovviamente, non lo fanno ottenere persistito
  • quando oggetto viene creato dalla fabbrica dal momento che un oggetto appena creato manca la componente fa riferimento a
  • quando l'oggetto viene de-serializzato in un lavoro al quarzo o in qualche altro luogo in quanto i riferimenti dei componenti transitorie ottenere spazzati

Prima di tutto, è brutto perché sto passando all'oggetto un riferimento al contesto dell'applicazione e mi aspetto che venga estratto dai riferimenti ai componenti necessari. Questa non è iniezione, è tirata diretta.

In secondo luogo, è brutto codice, perché in tutti quei luoghi di cui ho bisogno logica per l'iniezione di un appContext

In terzo luogo, è soggetto ad errori perché devo ricordare per iniettare in tutti quei luoghi per tutti quegli oggetti, che è più difficile di quanto sembri.

Ci deve essere un modo migliore e spero che tu possa far luce su di esso.

risposta

0

Forse quello che vuoi è un tipo sull'oggetto di riferimento, che si serializza come riferimento globale (un URI per esempio) e che sarebbe in grado di resuscitare come proxy quando non serializzato altrove.

4

Mi permetto di dire che ci sono molte sfumature di grigio tra avere un "modello di dominio anemico" e mettere tutti i tuoi servizi nei tuoi oggetti di dominio. E abbastanza spesso, almeno nei settori aziendali e nella mia esperienza, un oggetto potrebbe in realtà non essere altro che i soli dati; per esempio, ogni volta che le operazioni che possono essere eseguite su quel particolare oggetto dipendono da una moltitudine di altri oggetti e da un contesto localizzato, ad esempio un indirizzo.

Nella mia recensione della letteratura basata sul dominio sulla rete, ho trovato molte idee e scritti vaghi, ma non ero in grado di trovare un esempio appropriato e non banale di dove i confini tra i metodi e le operazioni dovrebbe mentire e, per di più, come implementarlo con lo stack tecnologico attuale. Quindi, ai fini di questa risposta, farò un piccolo esempio per illustrare i miei punti:

Consideriamo l'antico esempio di ordini e ordini. Un modello di dominio "anemica" sarebbe simile:

class Order { 
    Long orderId; 
    Date orderDate; 
    Long receivedById; // user which received the order 
} 

class OrderItem { 
    Long orderId;  // order to which this item belongs 
    Long productId; // product id 
    BigDecimal amount; 
    BigDecimal price; 
} 

A mio parere, il punto del Domain-Driven Design è quello di utilizzare le classi per modellare meglio le relazioni tra entità. Quindi, un modello non-anemici sarebbe simile:

class Order { 
    Long orderId; 
    Date orderDate; 
    User receivedBy; 
    Set<OrderItem> items; 
} 

class OrderItem { 
    Order order; 
    Product product; 
    BigDecimal amount; 
    BigDecimal price; 
} 

Presumibilmente, si sarebbe utilizzando una soluzione ORM per fare la mappatura qui. In questo modello, potresti scrivere un metodo come Order.calculateTotal(), che riassume tutto lo amount*price per ogni articolo dell'ordine.

Quindi, il modello sarebbe ricco, nel senso che le operazioni che hanno senso dal punto di vista aziendale, come calculateTotal, verranno inserite in un oggetto dominio Order. Ma, almeno a mio avviso, la progettazione basata sul dominio non significa che lo Order debba conoscere i servizi di persistenza. Questo dovrebbe essere fatto in un livello separato e indipendente. Le operazioni di persistenza non fanno parte del dominio aziendale, sono parte dell'implementazione.

E anche in questo semplice esempio, ci sono molte insidie ​​da considerare. L'intero Product deve essere caricato con ogni OrderItem? Se c'è un numero enorme di articoli dell'ordine e hai bisogno di un rapporto di riepilogo per un numero enorme di ordini, utilizzeresti Java, caricando oggetti in memoria e invocando calculateTotal() per ciascun ordine? Oppure una query SQL è una soluzione molto migliore, sotto ogni aspetto. Ecco perché una soluzione ORM decente come Hibernate offre meccanismi per risolvere con precisione questi tipi di problemi pratici: lazy-loading con proxy per la prima e HQL per quest'ultima. Quale vantaggio sarebbe un modello teoricamente valido, se la generazione di rapporti richiede anni?

Ovviamente, l'intera questione è piuttosto complessa, molto più che sono in grado di scrivere o considerare in una sola seduta. E non sto parlando da una posizione di autorità, ma da una semplice pratica quotidiana nella distribuzione di app aziendali. Spero che otterrai una risposta a questa risposta. Sentitevi liberi di fornire alcuni dettagli aggiuntivi ed esempi di ciò che hai a che fare con ...

Edit: Per quanto riguarda il servizio PriceQuery, e l'esempio di invio di una e-mail dopo che il totale è stato calcolato, mi farebbe un distinzione tra:

  1. il fatto che una e-mail deve essere inviato dopo il calcolo del prezzo
  2. quale parte di un ordine devono essere inviati? (Questo potrebbe anche includere, per esempio, modelli di posta elettronica)
  3. il metodo effettivo di invio di una e-mail

Inoltre, ci si deve chiedere, è l'invio di una e-mail una capacità intrinseca di un Order, o ancora un'altra cosa che può essere fatto con esso, come persistente, serializzazione in diversi formati (XML, CSV, Excel) ecc.

Cosa farei, e quello che considero un buon approccio OOP è il seguente. Definire un'interfaccia operazioni di preparazione e l'invio di una e-mail incapsulante:

interface EmailSender { 
    public void setSubject(String subject); 
    public void addRecipient(String address, RecipientType type); 
    public void setMessageBody(String body); 
    public void send(); 
} 

Ora, all'interno Order classe, definire un'operazione con la quale un ordine "sa" come inviare se stesso come una e-mail, utilizzando un mittente di posta elettronica:

class Order { 
... 
    public void sendTotalEmail(EmailSender sender) { 
     sender.setSubject("Order " + this.orderId); 
     sender.addRecipient(receivedBy.getEmailAddress(), RecipientType.TO); 
     sender.addRecipient(receivedBy.getSupervisor().getEmailAddress(), RecipientType.BCC); 
     sender.setMessageBody("Order total is: " + calculateTotal()); 
     sender.send(); 
    } 

Infine, si dovrebbe avere una facciata verso le operazioni dell'applicazione, un punto in cui avviene la risposta effettiva all'azione dell'utente. A mio avviso, è qui che dovresti ottenere (per Spring DI) l'effettiva implementazione dei servizi.Questo può, per esempio, essere la Spring MVC Controller classe:

public class OrderEmailController extends BaseFormController { 
    // injected by Spring 
    private OrderManager orderManager; // persistence 
    private EmailSender emailSender; // actual sending of email 

public ModelAndView processFormSubmission(HttpServletRequest request, 
              HttpServletResponse response, ...) { 
    String id = request.getParameter("id"); 
    Order order = orderManager.getOrder(id); 
    order.sendTotalEmail(emailSender); 

    return new ModelAndView(...); 
} 

Ecco cosa si ottiene con questo approccio:

  1. oggetti di dominio non contengono servizi, usano loro
  2. gli oggetti di dominio sono disaccoppiati dall'attuale implementazione del servizio (es. SMTP, invio in thread separati ecc.), dalla natura del meccanismo di interfaccia
  3. le interfacce di servizi sono generiche, riutilizzabili, ma non so tutti gli oggetti di dominio reali. Ad esempio, se l'ordine ottiene un campo aggiuntivo, è necessario modificare solo la classe Order.
  4. si può prendere in giro i servizi facilmente, e gli oggetti dominio di prova facilmente
  5. è possibile testare le implementazioni attuali servizi facilmente

Non so se questo è per gli standard di alcuni guru, ma è un down-to Approccio che funziona abbastanza bene nella pratica.

+0

quello che stai descrivendo bene, ma mi colpisce pareti su esempi più complessi. Cosa succede se il tuo ordine deve inviare un'e-mail ogni volta che viene calcolato il totale? Certo, potresti portarlo nel tuo Servizio, ma questo non è vero OOP, giusto? Martin F. non usa nemmeno un livello di servizio – rcampbell

+0

Si prega di consultare il mio "Another Edit".Mi piacerebbe davvero sentire i tuoi pensieri – rcampbell

1

L'approccio più semplice che posso pensare è aggiungere una logica al livello di accesso ai dati che inietterà un oggetto dominio con le sue dipendenze prima di restituirlo a un livello superiore (di solito chiamato livello di servizio). È possibile annotare le proprietà di ciascuna classe per indicare cosa occorre cablare. Se non si utilizza Java 5+, è possibile implementare un'interfaccia per ogni componente che deve essere iniettato, o addirittura dichiararlo tutto in XML e alimentare tali dati al contesto che eseguirà il cablaggio. Se si desidera essere fantasiosi, è possibile estrapolarlo in un aspetto e applicarlo a livello globale attraverso il livello di accesso ai dati in modo che tutti i metodi che estraggono gli oggetti di dominio li colleghino solo dopo che sono stati restituiti.

+0

Il tuo primo suggerimento (iniettarlo in DAO) Lo sto facendo ora, ma come ho detto ci sono problemi con la serializzazione, le fabbriche, ecc. Mi piace molto il tuo suggerimento di usare un aspetto + interfaccia. Ciò separerebbe la logica di iniezione e la renderebbe più pulita/più gestibile ... – rcampbell

+0

La gestione degli altri due scenari è impegnativa. Puoi strumentare la classe quando è caricata nella JVM. Fondamentalmente un altro approccio AOP ma molto diverso in termini di implementazione. Ecco alcune informazioni su come lo fanno in Terracotta: http://tinyurl.com/cgx8ne –

2

Regardinig

Che cosa succede se il vostro Ordine ha bisogno di inviare una e-mail ogni volta che il totale è calcolato?

Vorrei utilizzare eventi.
Se ha un significato per te quando un ordine calcola il suo totale, lascia che generi un evento come eventDispatcher.raiseEvent (new ComputedTotalEvent (this)).
Quindi ascoltate questo tipo di eventi e richiamate il vostro ordine come detto prima per lasciare che formatti un modello di email e lo invii.
Gli oggetti del dominio restano snelli, senza alcuna conoscenza di questo requisito.
In breve, dividi il tuo problema in 2 requisiti:
- Voglio sapere quando un ordine calcola il suo totale;
- Voglio inviare una e-mail quando un ordine ha un (nuovo e diverso) totale;

Problemi correlati