Gdzie zgłaszać zdarzenia domeny zależne od trwałości-usługi, repozytorium lub interfejsu użytkownika?
Mój ASP.NET aplikacja MVC3 / NHibernate ma wymóg odpalania i obsługi różnych zdarzeń związanych z obiektami mojej domeny. Na przykład obiekt Order
może mieć zdarzenia takie jak OrderStatusChanged
lub NoteCreatedForOrder
. W większości przypadków zdarzenia te powodują wysłanie wiadomości e-mail, więc nie mogę po prostu zostawić ich w aplikacji MVC.
Moje pytanie brzmi jaka warstwa aplikacji powinna rzeczywiście podnosić zdarzenia. Nie chcę uruchamiać zdarzeń, dopóki dany obiekt nie zostanie pomyślnie utrzymany (nie mogę wysłać e-maila, że notatka została utworzona, jeśli trwałość nie powiodła się).
Innym problemem jest to, że w niektórych przypadkach zdarzenie jest powiązane z obiektem, który znajduje się pod zbiorczym korzeniem. W powyższym przykładzie, Note
jest zapisywany przez dodanie go do kolekcji Order.Notes
i zapisanie kolejności. Problem polega na tym, że trudno jest ocenić, jakie zdarzenia powinny zostać wywołane Po zapisaniu Order
. Chciałbym uniknąć konieczności wyciągania bieżącej kopii obiektu i szukania różnic przed zapisaniem zaktualizowanej kopii.
-
Czy odpowiednie jest, aby UI informowało o tych wydarzeniach? Wie, jakie zdarzenia miały miejsce i może je odpalić dopiero po pomyślnym warstwa usług zapisuje obiekt. Coś po prostu wydaje się nie tak z kontrolerem odpalającym zdarzenia domeny.
-
Czy repozytorium powinno odpalić zdarzenia po pomyślnym utrzymywaniu się?
-
Czy powinienem oddzielić wszystkie zdarzenia, aby repozytorium przechowywało obiekt
Event
, który jest następnie przechwytywany przez usługę Pollera i , a następnie zamienione w Zdarzenie dla NServiceBus (lub obsługiwane bezpośrednio z usługi Pollera)? -
Jest lepszy sposób na to? Może aby obiekty mojej domeny ustawiały w kolejce zdarzenia, które są wywoływane przez warstwę usług dopiero po utrzymaniu obiektu?
-
Update: mam warstwę usług, ale wydaje się kłopotliwe i nadmierne, aby przejść przez proces porównania, aby określić, jakie zdarzenia powinny być wywołane, gdy dany zagregowany root jest zapisywany. Ponieważ niektóre z tych zdarzeń są ziarniste( np. "status zamówienia zmieniony"), myślę, że będę musiał odzyskać kopię DB obiekt, porównaj właściwości, aby utworzyć zdarzenia, zapisz nowy obiekt, a następnie wyślij zdarzenia do NServiceBus, gdy operacja zapisania zakończyła się pomyślnie.
Update
To, co zrobiłem, po odpowiedzi, którą opublikowałem poniżej (znacznie poniżej ), to wbudowanie w moje podmioty domenowe właściwości EventQueue
, która była List<IDomainEvent>
. Następnie dodałem zdarzenia jako zmiany w domenie, które pozwoliły mi zachować logikę w domenie, co moim zdaniem jest odpowiednie, ponieważ odpalam zdarzenia w oparciu o to, co dzieje się wewnątrz jednostki.
Następnie, gdy utrzymuję obiekt w warstwie usług, przetwarzam tę kolejkę i faktycznie wysyłam zdarzenia do magistrali usług. Początkowo planowałem użyć starszej bazy danych, która używała identyfikatora PKs, więc musiałem przetworzyć te zdarzenia, aby wypełnić identyfikator podmiotu, ale ostatecznie zdecydowałem się przełączyć na Guid.Comb
PK, który pozwala mi pominąć ten krok.
6 answers
Moim rozwiązaniem jest to, że podnosisz zdarzenia zarówno w warstwie domeny, jak i w warstwie usług.
Twoja domena:
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 });
}
}
Twój serwis:
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);
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-12 04:50:53
Zdarzenia domeny powinny być podnoszone w... domena. Dlatego są to zdarzenia domenowe.
public void ExecuteCommand(MakeCustomerGoldCommand command)
{
if (ValidateCommand(command) == ValidationResult.OK)
{
Customer item = CustomerRepository.GetById(command.CustomerId);
item.Status = CustomerStatus.Gold;
CustomerRepository.Update(item);
}
}
(a następnie w klasie Klienta, następujące):
public CustomerStatus Status
{
....
set
{
if (_status != value)
{
_status = value;
switch(_status)
{
case CustomerStatus.Gold:
DomainEvents.Raise(CustomerIsMadeGold(this));
break;
...
}
}
}
Metoda Raise zapisze Zdarzenie w magazynie zdarzeń. Może również wykonywać lokalnie zarejestrowane procedury obsługi zdarzeń.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 12:44:37
Wygląda na to, że potrzebujesz warstwy serwisowej . Warstwa usług to kolejna abstrakcja, która znajduje się między interfejsem lub kontrolerami a warstwą biznesową lub modelem domeny. To coś w rodzaju API do Twojej aplikacji. Kontrolery będą wtedy miały dostęp tylko do warstwy usług.
Następnie warstwa usług staje się odpowiedzialna za interakcję z modelem domeny.]}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();
}
//...
}
Często kończy się na obiektach 'manager', takich jak OrderManager
, aby współdziałać z zamówienia i warstwy usługowej do obsługi POCOs.
Nie. Pojawią się problemy, jeśli zostaną dodane nowe akcje, a deweloper nie będzie świadomy lub zapomni, że wiadomość e-mail powinna zostać wyłączona.Czy odpowiednie jest, aby UI informowało o tych wydarzeniach? Wie, jakie zdarzenia miały miejsce i może je uruchomić dopiero po pomyślnym zapisaniu obiektu przez warstwę usług. Coś po prostu wydaje się nie tak z kontrolerem odpalającym zdarzenia domeny.
Nie. repozytorium jest odpowiedzialne za zapewnienie abstrakcji nad dostępem do danych i nic więcejPowinien repozytorium odpala zdarzenia po pomyślnym utrzymaniu?
Czy jest na to lepszy sposób? Może aby obiekty mojej domeny ustawiały w kolejce zdarzenia, które są wywoływane przez warstwę usług dopiero po utrzymaniu obiektu?
Tak, wygląda na to, że powinno to być obsługiwane przez warstwę usług.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-04 16:35:54
Rozwiązanie okazało się oparte na implementacji tych metod rozszerzenia na obiekcie sesji NHibernate.
Byłem prawdopodobnie trochę niejasny z brzmieniem pytania. Powodem problemu architektonicznego był fakt, że obiekty NHibernate są zawsze w tym samym stanie, chyba że ręcznie je odłączysz i przejdziesz przez różnego rodzaju machinacje. To było coś, czego nie chciałem robić, aby określić, jakie właściwości zostały zmienione, a zatem jakie wydarzenia odpalić.
Odpalenie tych zdarzeń w Ustawieniach właściwości nie zadziała, ponieważ zdarzenia powinny wystrzelić tylko po utrzymaniu zmian, aby uniknąć odpalenia zdarzeń w operacji, która może ostatecznie zawieść.
Więc dodałem kilka metod do bazy repozytorium:
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);
}
Następnie, w metodzie mojego serwisu Save
, mogę zrobić coś takiego (pamiętaj, że używam tutaj NServiceBus, ale jeśli używasz statycznej klasy Udi Dahan ' s domain events to zadziała podobnie):
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);
Ponieważ w niektórych przypadkach Id
encji może nie być ustawiona, dopóki nie zostanie zapisana (używam kolumn całkowitych Identity
), może być konieczne uruchomienie po zatwierdzeniu transakcji, aby pobrać identyfikator, aby wypełnić właściwości w moich obiektach event. Ponieważ są to istniejące dane, nie mogę łatwo przełączyć się na identyfikator przypisany klientowi typu hilo.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-06 13:31:49
Posiadanie zdarzeń domeny działa dobrze, jeśli masz polecenia , wysłane do twojej warstwy usług . Następnie może aktualizować encje zgodnie z poleceniem i wywoływać odpowiednie zdarzenia domeny w pojedynczej transakcji.
Jeśli przenosisz same byty pomiędzy UI i service layer , bardzo trudno (a czasami nawet niemożliwe) jest określić, jakie zdarzenia w domenie miały miejsce, ponieważ nie są one jawne, ale ukryte pod state of the domain. / align = "left" /
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-04 17:09:05
Myślę, że powinieneś mieć usługę domenową, jak powiedział David Glenn.
Problem, na który wpadłem, polegał na tym, że warstwa usług skończy się na aby pobrać kopię niektórych obiektów przed zapisaniem zmodyfikowanej wersji do porównaj nowy z starym, a następnie zdecyduj, jakie wydarzenia powinien zostać zwolniony.
Usługa domeny powinna zawierać metody, które jasno określają, co chcesz zrobić z podmiotem domeny, takie jak: RegisterNewOrder, Createnoteforoorder, ChangeOrderStatus itp.
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));
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-11 10:18:49