Posts tagged DDDSample

O usługach

Zostałem niedawno zapytany, dlaczego w projekcie DDDSample.NET projekt “Application” nazywa się właśnie tak, a nie “Domain Services”. Zwróciło to moją uwagę na całkiem spory problem nazewnictwa związanego z DDD oraz ogólnie z architekturami. Jednym ze źródeł problemu zdaje się być niesamowicie przeładowane znaczeniowo słowo “usługa”. Ale po kolei…

Wspomniany projekt “Application” zawiera fasadę Modelu Domeny udostępniającą operacje biznesowe realizowane za pomocą tegoż modelu. Te operacje to coś w rodzaju transaction script-ów operujących na obiektach domeny. Nazwę zaczerpnąłem oczywiście wprost z Java-owego oryginału, którego dokumentacja tak opisuje ten element architektury:

The application layer is responsible for driving the workflow of the application, matching the use cases at hand. These operatios are interface-independent and can be both synchronous or message-driven. This layer is well suited for spanning transactions, high-level logging and security.

The application layer is thin in terms of domain logic – it merely coordinates the domain layer objects to perform the actual work.

Przyznam szczerze, że nazwa “Application” nie jest najszczęśliwsza na świecie, bo tak na dobrą sprawę, nie mówi nic o przeznaczeniu tej warstwy. Można ją znaleźć także w innych przykładach, np. w architekturze cebulowej Jeffreya Palermo. Różnica polega jednak na tym, że u Palermo nazwa brzmi “Application Services”, co niesie już ze sobą jakieś znaczenie — mamy do czynienia z usługami.

No właśnie. Dlaczego pisałem na wstępie, że winne jest słowo “usługa”? Ponieważ tak naprawdę ja najchętniej z “Application Services” zostawiłbym “Services” jako nazwę mojej warstwy. Zauważcie, że pasuje ona o wiele lepiej pasuje do przytoczonego opisu. Niestety usługi występują już w tylu kontekstach w naszej terminologii, że wprowadzenie kolejnych “usług” byłoby niewskazane.

Być może lepiej byłoby w ogóle zrezygnować z terminologii wywodzącej się z “Application Services”? Można się odwołać np. do tego, że warstwa ta implementuje przypadki użycia i nazwać ją “Use Cases”. Albo, podchodząc do problemu od strony technicznej, zauważyć że jest to implementacja wzorca fasady — “Model Facade”. A jak Wy nazywacie projekty takiego rodzaju w swoich architekturach?

Druga część pytania dotyczy domain services — usług modelu domeny. Wydaje się, że jest jeszcze wiele wątpliwości związanych z usługami w Domain Driven Design. Moje rozumienie tego termin jest następujące.

Usługi modelu domeny pozwalają wyrazić operacje, które nie mają naturalnie przypisanego obiektu wykonującego je lub są realizowane przez obiekty zewnętrzne w stosunku do modelu. Na przykład IPricingService to usługa zwracająca ceny produktów na podstawie danych uzyskanych z innego systemu za pośrednictwem webserwisu. Na poziomie modelu reprezentowana jest przez interfejs, aby nie uzależniać go od szczegółów implementacyjnych.

Osoba, która zadała mi pytanie rozumiała usługi domenowe jako sposób na wykonanie pewnych bardziej skomplikowanych operacji biznesowych poprzez orkiestrację wywołań na obiektach modelu, czyli… dokładnie to, co u mnie kryje znajduje się w warstwie “Application”. Podobne opinie znalazłem też na kilku znanych blogach, np. tu. Cóż, nie zgadzam się z takim podejściem. Nie widzę sensu, aby definiować takie byty. Co więcej, usługi domenowe w takim znaczeniu miałyby bardzo niebezpieczną tendencję to wysysania logiki biznesowej z obiektów modelu.

Podsumowując, w dużym skrócie i uproszczeniu:

  • Następnym razem napewno nazwę swoją warstwę “Application” inaczej. Skłaniam się ku “Use Cases”.
  • Usługi domenowe mogą reprezentować albo operacje nie posiadające wykonawcy (algorytmy), albo być abstrakcją dla synchronicznych usług implementowanych na zewnątrz. Oba przypadki powinny być bardzo rzadkie.

The application layer is responsible for driving the workflow of the application, matching the use cases at hand. These operatios are interface-independent and can be both synchronous or message-driven. This layer is well suited for spanning transactions, high-level logging and security.

The application layer is thin in terms of domain logic – it merely coordinates the domain layer objects to perform the actual work

VN:F [1.8.7_1070]
Rating: 3.7/5 (3 votes cast)

Event sourcing in DDDSample: reviewing Mark Nijhof’s solution

As CQRS version of DDDSample is getting mature, I am switching my development efforts to event sourcing support. For those of you who don’t know that yet, event sourcing is a pattern which encourages persisting not snapshots of data in particular moments in time, but rather events which describe how these data changes. Then, data snapshots (not only the latest, but also historic ones) can be reconstructed by applying these events starting from the ‘beginning of time’. More about defining event sourcing and CQRS can be found here.

I’ve started by looking at Marc Nijhof’s excellent series of posts (starting here) describing event sourcing. Mark has built a sample application based on what he had learnt about CQRS during Greg Young’s course.

Mark’s solution is fairly simple. In the core there is a domain assembly containing classes that model domain concepts like Account and Client. These inherit and use additional infrastructure assemblies which provide event sourcing features.

One thing that looks odd to me is usage of memento pattern to store snapshots of aggregates. I don’t understand why can’t they (the aggregates) be simply serialized as they are. Maybe there is a reason behind this, but I don’t see it, at least for now. I marked this area for further investigation.

Moving away from the core, there are several supporting assemblies which contain commands, command handlers, events and so on. Personally, I don’t see a point in separating some of them. There is an obvious reason why commands and command handlers are in separate assemblies, but the events? They are part of the domain model, at least for me.

After more careful analysis I noticed that there is no notion of messages in Marks sample solution. The events themselves are passed from command side to query side. That is probably the reason why the events are defined in separate assembly — if they are to be used in reporting, it would be nice not to have to reference whole domain model assembly, only the events.

Going further, Mark is using a custom O/RMish solution to implement query/reporting side. I am not a big fan of custom solutions and I probably would use an off-the-shelve one, even if it is slightly more complex then necessary. I am planing to use NHibernate, as in the non-event-sourcing CQRS version.

Anyway, Mark did great job of describing event sourcing concepts to the public. I probably wouldn’t be able to start writing my own implementation in DDDSample without first reading his code. Thanks a lot!

VN:F [1.8.7_1070]
Rating: 3.0/5 (2 votes cast)

LINQ 2 SQL in DDDSample

Yesterday I committed a preview of LINQ to SQL branch of CQRS version of DDDSample. As you might guess, I replaced NHibernate in reporting subsystem with L2S. Why? Because I wanted to show that reporting is simple in terms of data access and doesn’t need such a sophisticated framework as NHibernate.

I must confess that I am not very proficient in L2S so the implementation might not be optimal. My goal is to just make it working.

For now L2S version is only available through source code download. I encourage all of you to take a look at the implementation. It is quite simple, yet I think it shows the power of CQRS.

If you think about implementing a production system using DDD/CQRS approach, there is one additional think worth noticing. Thanks to the separation of command and query handling you can assign less experienced developers (or ones more focused on UI then on modeling) to query side. They probably can handle implementing it using such a simple tool like LINQ to SQL. In the same time you can assign your senior staff to command processing. This is where business logic is implemented and where good coding and modeling skills bring most value.

VN:F [1.8.7_1070]
Rating: 3.0/5 (2 votes cast)

CQRS w praktyce

Dużo piszę ostatnio o CQRS (Command Query Responsibility Segregation), ale nie pokazałem ani razu jak to podejście wygląda w praktyce. Postaram się dziś naprawić to niedopatrzenie. Posłużę się w tym celu projektem DDDSample w najnowszej wersji CQRS.

Układ solution

Tak wygląda układ solution Visual Studio:

solution layout

Kod podzielony jest na cztery główne obszary:

  • Domain — tutaj znajduje się logika biznesowa aplikacji, której zadaniem jest przetwarzanie transakcji (komend). Oprócz centralnego projektu “Domain”, w obszarze tym znajdują się dwa wspomagające. Idąc od góry, pierwszy z nich zawiera obsługę zdarzeń domenowych a’la Udi Dahan, drugi zaś kod dostępu do danych (w oparciu o NHiberate)
  • Reporting — ten obszar ma za zadanie obsługiwać zapytania o dane do wyświetlenia na GUI. W razie potrzeby może on także służyć do generowania raportów. Główny projekt (“Reporting”) zawiera definicję struktur danych. Pozostałe to dostęp do danych oraz obsługa komunikatów reprezentujących zdarzenia aktualizacji modelu.
  • Infrastructure — tutaj znajduje się wszelki kod nie związany bezpośrednio z problemem, ale w jakiś sposób wspierający jego rozwiązanie, np. projekt “Messages” zawierający komunikaty przesyłane między podsystemem obsługi komend, a podsystemem obsługi zapytań.
  • W najwyższym poziomie hierarchii, bezpośrednio pod solution znajdują się dwa projekty scalające: “Application” oraz “UI”. Ten pierwszy stanowi fasadę ukrywającą złożoność całego rozwiązania przed tym drugim. “UI” to zwykły interfejs użytkownika wykorzystujący ASP.NET MVC.

Workflow

Skoro już wiecie, jak z grubsza wygląda rozwiązanie, chciałbym teraz omówić zachowanie systemu podczas standardowej interakcji, która przedstawia się tak:

Diagram przepływu CQRSNa powyższym diagramie MDK oznacza Model Domeny dla obsługi komend (projekt “Domain”), a MDZ to model dla raportowania (projekt “Reporting”).

W pierwszym żądaniu użytkownik systemu chce zobaczyć dane związane z obiektem biznesowym. System ładuje je z prosto z relacyjnej bazy danych MDZ. Drugie żądanie to już modyfikacja danych. Jest ona wykonywana na obiektach Modelu Domeny, które następnie są zapisywane w formie danych relacyjnych. Dokonane zmiany są także (w jakiejś formie) publikowane do MDZ, gdzie służą do aktualizacji struktur danych.

Zobaczmy, jak wygląda realizacja tego schematu w aplikacji DDDSample. Posłużę się przykładem komendy “Zmień docelową lokalizację dla towaru”.

Pobranie danych

Operacja pobrania danych zaczyna się od odpowiedniej akcji kontrolera:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult ChangeDestination(string trackingId)
{
   Reporting.Cargo cargo = _bookingFacade.LoadCargoForRouting(trackingId);
   //...

która odwołuje się do fasady:

public Reporting.Cargo LoadCargoForRouting(string trackingId)
{
   Reporting.Cargo c = _cargoDataAccess.Find(trackingId);
   if (c == null)
   {
      throw new ArgumentException("Cargo with specified tracking id not found.");
   }
   return c;
}

Fasada z kolei wykorzystuje bezpośrednio obiekt dostępu do danych:

public Cargo Find(string trackingId)
{
   const string query = @"from DDDSample.Reporting.Cargo c where c.TrackingId = :trackingId";
   return _sessionFactory.GetCurrentSession().CreateQuery(query).SetString("trackingId", trackingId)
      .UniqueResult<Cargo>();
}

Jak widzicie, nie ma tu żadnego przekształcenia danych po ich zwróceniu przez NHibernate. Żadnego DTO. Ponieważ kod podsystemu obsługi zapytań nie jest szczególnie wartościowy, mogę pozwolić sobie na nieco luźniejszą politykę zarządzania zależnościami. Na przykład obiekt dostępu do danych (CargoDataAccess) wykorzystywany jest jako konkretna klasa — nie mam jego abstrakcyjnej definicji. Nie muszę dbać o długowieczność tej części systemu. Kiedy pojawi się lepsza technologia, po prostu wyrzucę całą implementację, począwszy od fasady, aż do samego dołu.

Tak naprawdę użycie NHibernate tutaj może być nawet traktowane jako nadmierne skomplikowanie. Linq2SQL byłoby pewnie lepsze. Ale to temat na osobną notkę…

Wykonanie komendy

Wykonanie komendy także zaczyna się od akcji na kontrolerze. Zwróćcie uwagę, że tym razem akcja jest odpowiedzią na żądanie POST.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult ChangeDestination(string trackingId, string destination)
{
   _bookingFacade.ChangeDestination(trackingId, destination);
   return RedirectToDetails(trackingId);
}

Co kryje się za metodą fasady?

public void ChangeDestination(string trackingId, string destination)
{
   _bookingService.ChangeDestination(new TrackingId(trackingId),
      new UnLocode(destination));
}

Przekazujemy sterowanie do warstwy Application (obiekt _bookingService). Zaczyna się właściwe wykonanie komendy. W tym momencie włącza się lekki moduł programowania aspektowego (AOP) w moim kontenerze Unity, który zawija całe wywołanie ChangeDestination w transakcję.

Właściwa implementacja komendy zmiany punktu docelowego wygląda tak:

public void ChangeDestination(TrackingId trackingId, UnLocode destinationUnLocode)
{
   Location destination = _locationRepository.Find(destinationUnLocode);
   Cargo cargo = _cargoRepository.Find(trackingId);

   cargo.SpecifyNewRoute(destination);
}

Pierwszym krokiem utworzenie obiektu reprezentującego nową lokalizację docelową towaru. W tym celu wykorzystujemy (abstrakcyjne) repozytorium lokalizacji. Następnie pobieramy z bazy obiekt towaru, którego docelowa lokalizacja ma być zmieniona. Ostatecznie wywołujemy odpowiednią metodę na obiekcie reprezentującym towar. Wygląda ona tak:

public virtual void SpecifyNewRoute(Location.Location destination)
{
   if (destination == null)
   {
      throw new ArgumentNullException("destination");
   }
   RouteSpecification routeSpecification = new RouteSpecification(_routeSpecification.Origin, destination, _routeSpecification.ArrivalDeadline);

   Delivery delivery = Delivery.DerivedFrom(routeSpecification, _itinerary, _lastHandlingEvent);
   CargoDestinationChangedEvent @event = new CargoDestinationChangedEvent(this, routeSpecification, _routeSpecification, delivery);
   _routeSpecification = routeSpecification;
   DomainEvents.Raise(@event);
}

Pomińmy walidację argumentów. Zaczynamy od stworzenia obiektu reprezentującego nową specyfikację trasy tego towaru. RouteSpecification jest tzw. value object-em, więc nie modyfikujemy istniejącej instancji, ale tworzymy nową. Następnie wykorzystujemy obiekt Delivery do określenia nowych danych dotyczących dostawy towaru. Dane te w kolejne linii są wykorzystywane do utworzenia zdarzenia informującego o zmianie lokalizacji docelowej dla towaru. Na koniec ustawiamy nową specyfikację (a wraz z nią lokalizację docelową) i publikujemy zdarzenie.

Asynchroniczna synchronizacja danych

:-) Fajnie mi się napisało. W klasycznym systemie przetwarzanie byłoby już zakończone, ale nie w CQRS. Teraz ma miejsce ostatni etap, jakim jest uwzględnienie dokonanych zmian do w bazie obsługującej zapytania. Pierwsza faza właściwie już się wykonała — była to publikacja zdarzenia. Kolejna to jego obsługa. Zajmuje się nią następująca klasa (zlokalizowana w projekcie “Domain.EventHandlers”):

public class CargoDestinationChangedEventHandler : IEventHandler<CargoDestinationChangedEvent>
{
   private readonly IBus _bus;

   public CargoDestinationChangedEventHandler(IBus bus)
   {
      _bus = bus;
   }

   public void Handle(CargoDestinationChangedEvent @event)
   {
      _bus.Publish(new CargoDestinationChangedMessage
                      {
                         TrackingId = @event.Cargo.TrackingId.IdString,
                         Origin = @event.NewSpecification.Origin.Name,
                         Destination = @event.NewSpecification.Destination.Name,
                         ArrivalDeadline = @event.NewSpecification.ArrivalDeadline
                      });
   }
}

Jej zadaniem jest zamiana zdarzenia domenowego na komunikat NServiceBus, który jest następnie publikowany “na szynie”. Dalej dzieje się magia NServiceBus i MSMQ, która prowadzi do tego, że wiadomość zostanie obsłużona przez przeznaczony do tego obiekt znajdujący się w projekcie “Reporting.MessageHandlers”:

public class CargoDestinationChangedMessageHandler : AbstractMessageHandler<CargoDestinationChangedMessage>
{
   private readonly CargoDataAccess _cargoDataAccess;

   public CargoDestinationChangedMessageHandler(CargoDataAccess cargoDataAccess, ISessionFactory sessionFactory)
      : base(sessionFactory)
   {
      _cargoDataAccess = cargoDataAccess;
   }

   protected override void DoHandle(CargoDestinationChangedMessage message)
   {
      Cargo cargo = _cargoDataAccess.Find(message.TrackingId);
      cargo.UpdateRouteSpecification(message.Origin, message.Destination, message.ArrivalDeadline);
   }
}

Obsługa komunikatu polega na pobraniu z bazy danych struktury reprezentującej towar, a następnie aktualizacji danych dotyczących specyfikacji trasy.

Podsumowanie

Mam nadzieję, udało mi się nie wystraszyć Was nadmierną ilością kodu. Chciałem pokazać, jak naprawdę wygląda CQRS oraz, że wbrew obiegowej opinii, nie jest to wcale coś strasznego. Nie wymaga wcale większych, niż wykorzystanie wzorca DTO, nakładów pracy, a pozwala na uzyskanie znacznie większej swobody polegającej na względnie niezależnym rozwoju podsystemów obsługi komend i zapytań.

VN:F [1.8.7_1070]
Rating: 3.0/5 (2 votes cast)

DDDSample 0.6

DDDSample.Net version 0.6 have just been released here on CodePlex. Minor changes include moving from in-memory database fakes and SQLExpress to SQLite in both tests and UI.

This finally frees the project from any external dependencies which need to be installed separately. SQLExpress caused many problems so I am very happy I managed to get rid of it. One disadvantage of SQLite is that when working in in-memory mode, it loses all data after closing connection. In thin client scenario where I want to have connection per request this is unacceptable and forced me to use SQLite in file mode. This means that, unfortunately, you have to manually add IIS account write privileges to App_Data directory in DDDSample.Net instalation folder.

The major change is including CQRS (Command and Query Responsibility Segregation) variant of the solution. CQRS is about separating application architecture into two subsystems: one for processing commands and one for processing queries. Command processing uses Domain Model to capture business logic of the problem domain. In classic solution the same model is used to populate the UI which makes it polluted with lots of UI related code. In CQRS, however, Domain Model have only one purpose — support command processing. UI queries (as well as complicated reporting queries) are processed by the other subsystem which can (and should) be optimized for reading (which could mean, for example, suing star schema).

CQRS comes in many different flavors regarding implementation. The most import ones come from Udi Dahan and Greg Young (which is best described by Mark Nijhof). My own implementation is somewhat close to Udi’s ideas (at least I hope so;-). What I haven’t done (yet) is describing commands as separate classes.

Please download the source code and compare the old (classic) implementation with the new one. Any ideas what can be done better?

VN:F [1.8.7_1070]
Rating: 3.0/5 (2 votes cast)