Domanda

La mia applicazione ASP.NET MVC3 / NHIBERNATE ha l'obbligo di sparare e gestire una varietà di eventi relativi agli oggetti del mio dominio. Ad esempio, un Order L'oggetto potrebbe avere eventi come OrderStatusChanged o NoteCreatedForOrder. Nella maggior parte dei casi questi eventi si traducono in un'e -mail inviata, quindi non posso semplicemente lasciarli nell'app MVC.

Ho letto Udi Dahan's Eventi di dominio E dozzine di altri pensieri su come fare questo genere di cose e ho deciso di usare un host basato su Nservicebus che gestisce i messaggi di eventi. Ho fatto alcuni test di prova del concetto e questo sembra funzionare bene.

La mia domanda è Quale livello dell'applicazione dovrebbe effettivamente sollevare gli eventi. Non voglio licenziare gli eventi fino a quando l'oggetto in questione non è stato persistito con successo (non posso inviare un'e -mail che è stata creata una nota se la persistenza non è riuscita).

Un'altra preoccupazione è che in alcuni casi un evento sia legato a un oggetto che è sotto una radice aggregata. Nell'esempio sopra, a Note viene salvato aggiungendolo al Order.Notes raccolta e salvataggio dell'ordine. Questo pone un problema in quanto rende difficile valutare quali eventi dovrebbero essere licenziati quando un Order è salvato. Vorrei evitare di dover tirare una copia attuale dell'oggetto e cercare differenze prima di salvare la copia aggiornata.

  • È appropriato per l'interfaccia utente sollevare questi eventi? Sa quali eventi si sono verificati e può licenziarli solo dopo aver fatto salvare l'oggetto il livello di servizio. Qualcosa sembra solo di avere un controller che fa scattare eventi di dominio.

  • Il repository dovrebbe spegnere eventi dopo aver persistente con successo?

  • Dovrei separare del tutto gli eventi, avere il deposito archivio un Event oggetto che viene quindi raccolto da un servizio di pollice e poi trasformato in un evento per NServiceBus (o gestito direttamente dal servizio di pollice)?

  • C'è un modo migliore per farlo? Forse avere i miei oggetti di dominio in coda eventi che vengono licenziati dal livello di servizio solo dopo che l'oggetto è persistito?

  • Aggiornare: Ho un livello di servizio, ma sembra ingombrante ed eccessivo per farlo passare attraverso un processo di confronto per determinare quali eventi dovrebbero essere licenziati quando viene salvata una determinata radice aggregata. Poiché alcuni di questi eventi sono granulari (ad esempio "stato dell'ordine cambiato"), penso che dovrei recuperare una copia DB dell'oggetto, confrontare le proprietà per creare eventi, salvare il nuovo oggetto, quindi inviare gli eventi a Nservicebus quando L'operazione di salvataggio completata correttamente.

Aggiornare

Quello che ho finito per fare, in seguito alla risposta che ho pubblicato di seguito (molto sotto), doveva costruire nelle mie entità di dominio e EventQueue proprietà che era un List<IDomainEvent>. Ho quindi aggiunto eventi mentre le modifiche al dominio lo hanno meritato, il che mi ha permesso di mantenere la logica all'interno del dominio, che credo sia appropriata poiché sto sparando eventi in base a ciò che sta accadendo all'interno di un'entità.

Quindi, quando persisto l'oggetto nel mio livello di servizio, elaboro quella coda e in realtà invio gli eventi al bus di servizio. Inizialmente, avevo intenzione di utilizzare un database legacy che utilizzava PKS di identità, quindi ho dovuto post elaborare questi eventi per popolare l'ID dell'entità, ma alla fine ho deciso di passare a un Guid.Comb PK che mi permette di saltare quel passaggio.

È stato utile?

Soluzione 4

La soluzione si è rivelata basata sull'implementazione questi metodi di estensione Sull'oggetto sessione nhibernate.

Probabilmente non ero un po 'poco chiaro con la formulazione della domanda. L'intero motivo della questione architettonica era il fatto che gli oggetti nhibernati sono sempre nello stesso stato a meno che tu non li non possa non proteggere e attraversa ogni tipo di macchinazioni. Era qualcosa che non volevo fare per determinare quali proprietà erano state cambiate e quindi quali eventi sparare.

La licenziamento di questi eventi nei setter di proprietà non funzionerebbe perché gli eventi dovrebbero essere persistiti solo dopo che i cambiamenti sono stati persistiti, per evitare di sparare eventi su un'operazione che alla fine potrebbe fallire.

Quindi quello che ho fatto è stato aggiungere alcuni metodi alla mia base di repository:

public bool IsDirtyEntity(T entity)
{
    // Use the extension method...
    return SessionFactory.GetCurrentSession().IsDirtyEntity(entity);
}

public bool IsDirtyEntityProperty(T entity, string propertyName)
{
    // Use the extension method...
    return SessionFactory.GetCurrentSession().IsDirtyProperty(entity, propertyName);
}

Quindi, nel mio servizio Save Metodo, posso fare qualcosa del genere (ricorda che sto usando nservicebus qui, ma se stavi usando gli eventi di dominio di Udi Dahan Classe statica, funzionerebbe in modo simile):

var pendingEvents = new List<IMessage>();
if (_repository.IsDirtyEntityProperty(order, "Status"))
    pendingEvents.Add(new OrderStatusChanged()); // In reality I'd set the properties of this event message object

_repository.Save(order);
_unitOfWork.Commit();

// If we get here then the save operation succeeded
foreach (var message in pendingEvents)
    Bus.Send(message);

Perché in alcuni casi il Id dell'entità potrebbe non essere impostato fino a quando non viene salvato (sto usando Identity Colonne interi), potrei dover eseguire dopo aver commesso la transazione per recuperare l'ID per popolare le proprietà nei miei oggetti eventi. Poiché si tratta di dati esistenti, non riesco facilmente a passare a un tipo di ID assegnato al client.

Altri suggerimenti

La mia soluzione è che sollevi eventi sia nel livello di dominio che nel livello di servizio.

Il tuo dominio:

public class Order
{
    public void ChangeStatus(OrderStatus status)
    {
        // change status
        this.Status = status;
        DomainEvent.Raise(new OrderStatusChanged { OrderId = Id, Status = status });
    }

    public void AddNote(string note)
    {
        // add note
        this.Notes.Add(note)
        DomainEvent.Raise(new NoteCreatedForOrder { OrderId = Id, Note = note });
    }
}

Il tuo servizio:

public class OrderService
{
    public void SubmitOrder(int orderId, OrderStatus status, string note)
    {
        OrderStatusChanged orderStatusChanged = null;
        NoteCreatedForOrder noteCreatedForOrder = null;

        DomainEvent.Register<OrderStatusChanged>(x => orderStatusChanged = x);
        DomainEvent.Register<NoteCreatedForOrder>(x => noteCreatedForOrder = x);

        using (var uow = UnitOfWork.Start())
        {
            var order = orderRepository.Load(orderId);
            order.ChangeStatus(status);
            order.AddNote(note);
            uow.Commit(); // commit to persist order
        }

        if (orderStatusChanged != null)
        {
            // something like this
            serviceBus.Publish(orderStatusChanged);
        }

        if (noteCreatedForOrder!= null)
        {
            // something like this
            serviceBus.Publish(noteCreatedForOrder);
        }
    }
}

Gli eventi del dominio dovrebbero essere sollevati in ... il dominio. Ecco perché sono eventi di dominio.

public void ExecuteCommand(MakeCustomerGoldCommand command)
{
    if (ValidateCommand(command) == ValidationResult.OK)
    {
        Customer item = CustomerRepository.GetById(command.CustomerId);
        item.Status = CustomerStatus.Gold;
        CustomerRepository.Update(item);
    }
}

(e poi nella classe dei clienti, quanto segue):

public CustomerStatus Status
{
    ....
    set
    {
        if (_status != value)
        {
            _status = value;
            switch(_status)
            {
                case CustomerStatus.Gold:
                    DomainEvents.Raise(CustomerIsMadeGold(this));
                    break;
                ...
            }
        }
    }

Il metodo di raccolta memorizzerà l'evento nel negozio di eventi. Può anche eseguire gestori di eventi registrati localmente.

Sembra che tu abbia bisogno di un Livello di servizio. Un livello di servizio è un'altra astrazione che si trova tra il front -end o i controller e il livello aziendale o il modello di dominio. È un po 'come un'API nella tua applicazione. I controller avranno quindi accesso solo al tuo livello di servizio.

Diventa quindi la responsabilità del livello di servizio di interagire con il tuo modello di dominio

public Order GetOrderById(int id) {
  //...
  var order = orderRepository.get(id);
  //...
  return order;
}

public CreateOrder(Order order) {
  //...
  orderRepositroy.Add(order);
  if (orderRepository.Submitchanges()) {
    var email = emailManager.CreateNewOrderEmail(order);
    email.Send();
  }
  //...
}

È comune finire con oggetti 'manager' come OrderManager per interagire con gli ordini e il livello di servizio per gestire i POCOS.

È appropriato per l'interfaccia utente sollevare questi eventi? Sa quali eventi si sono verificati e può licenziarli solo dopo aver fatto salvare l'oggetto il livello di servizio. Qualcosa sembra solo di avere un controller che fa scattare eventi di dominio.

No. Finirai con problemi se vengono aggiunte nuove azioni e uno sviluppatore non è consapevole o dimentica che un'e -mail dovrebbe essere licenziata.

Il repository dovrebbe spegnere eventi dopo aver persistente con successo?

No. La responsabilità del repository è quella di fornire un'astrazione sull'accesso ai dati e nient'altro

C'è un modo migliore per farlo? Forse avere i miei oggetti di dominio in coda eventi che vengono licenziati dal livello di servizio solo dopo che l'oggetto è persistito?

Sì, sembra che questo debba essere gestito dal tuo livello di servizio.

Avere eventi di dominio funziona bene se hai Comandi, inviato al tuo livello di servizio. Può quindi aggiornare le entità in base a un comando e sollevare eventi di dominio corrispondenti in una singola transazione.

Se stai muovendo le entità da sole tra Ui e livello di servizio, diventa molto difficile (e talvolta anche impossibile) determinare quali eventi di dominio sono avvenuti, poiché non sono resi espliciti, ma nascosti sotto lo stato delle entità.

Penso che tu abbia un servizio di dominio, come ha detto David Glenn.

Il problema che mi sono imbattuto era che il livello di servizio avrebbe finito per dover tirare una copia di alcuni oggetti prima di salvare la versione modificata per confrontare quella nuova contro quella vecchia e quindi decidere quali eventi dovrebbero essere licenziati.

Il tuo servizio di dominio dovrebbe contiene metodi che indicano chiaramente ciò che vuoi fare con la tua entità di dominio, come: RegisterNeworder, CreateNoteForOrder, ChangeRorderstatus ecc.

public class OrderDomainService()
{
    public void ChangeOrderStatus(Order order, OrderStatus status)
    {
        try
        {
            order.ChangeStatus(status);
            using(IUnitOfWork unitOfWork = unitOfWorkFactory.Get())
            {
                IOrderRepository repository = unitOfWork.GetRepository<IOrderRepository>();
                repository.Save(order);
                unitOfWork.Commit();
            }
            DomainEvents.Publish<OrderStatusChnaged>(new OrderStatusChangedEvent(order, status));
        }

    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top