Posts tagged DDD
Domain Driven Design, czyli programowanie przez modelowanie
Jun 28th
Podczas 58. spotkania Krakowskiej Grupy Developerów .NET miałem przyjemność poprowadzić prezentację pt. “Domain Driven Design, czyli programowanie przez modelowanie”. Ponieważ zakładam, że nie wszyscy jesteście z Krakowa i uczestniczyliście w spotkaniu, pozwolę sobie opisać o czym mówiłem. Prezentację możecie obejrzeć lub ściągnąć (format pptx) stąd (via SlideShare), a przykłady w kodzie stąd.
Rozwiązywanie problemów
Na początku chciałbym skontrastować “klasyczne” podejście do rozwiązywania problemów z podejściem wykorzystującym model. Można to zilustrować następująco:
W wersji klasycznej programista implementuje bezpośrednio wymagania, tak jak zostały one spisane przez analityka (zwykle w formie 10/100/miliona linii “The system shall…”). Dobrze, jeśli w ten proces wpleciona jest pętla sprzężenia zwrotnego, która sprawia, że implementacja pierwszych wymagań ma wpływ na dalszy przebieg procesu analizy.
W podejściu “Domain-Driven” tak naprawdę nie istnieją role analityka i programisty. Istnieje tylko jedna rola — modelarz. Modelarz zajmuje się budową modelu rzeczywistości użytecznego w rozwiązaniu konkretnego problemu. Współpracuje on bardzo blisko z ekspertami dziedzinowymi, przetwarzając posiadaną przez nich wiedzę na spójny, niesprzeczny, a co najważniejsze, wykonywalny, model. Dopiero na bazie tego modelu tworzone jest ostateczne rozwiązanie. Możemy odnaleźć dwie pętle sprzężenia zwrotnego: pierwsza dotyczy samego modelu (im więcej modelarz stworzył, tym lepiej rozumie problem), a druga modelu w kontekście aplikacji (im więcej wiemy, co ma robić aplikacja, tym lepiej wiemy, które aspekty należy dokładniej modelować).
Cechy modelu
Skoro już wiemy, do czego przydaje się model, warto się zastanowić jakie cechy powinien posiadać dobry model. Oto moja osobista lista w kategorii “model powinien być”:
Użyteczny
Nie ma sensu budowa idealnego modelu rzeczywistości, ponieważ byłby on tak samo skomplikowany jak ta rzeczywistość. Z punktu widzenia złożoności obliczeniowej jest to niewykonalne. Znane powiedzenie mówi, że wszystkie modele są błędne, ale niektóre są użyteczne. Model, który budujemy na potrzeby naszego systemu powinien odzwierciedlać tylko ten fragment rzeczywistości, w ramach którego działa ten system i tylko pod kątem czynności bezpośrednio związanych z jego funkcjonowaniem.
Hermetyczny
Model jest kodyfikacją pewnych reguł rządzących modelowanym wycinkiem rzeczywistości. Reguły te nie powinny wyciekać z modelu, co oznacza, że jego klienci (użytkownicy) nie powinni musieć o nich wiedzieć. Przykładem może być reguła, że w modelu programu HR pracownik może mieć w danym momencie co najwyżej jednego pracodawcę. To model, a nie klient, powinien zadbać o to, aby przy zmianie pracy pracownik został odłączony od poprzedniego pracodawcy.
Podatny na modyfikacje
Jest to sprzeczne z zasadą Open-Closed Principle (open for extensions, closed for modifications), jednak w przypadku modeli sprawdza się bardzo dobrze. Aby model był nadążał za szybko zmieniającą się rzeczywistością, sam także musi się zmieniać. Nie chcemy przecież, aby skostniały model aplikacji był dla organizacji jej używającej hamulcem rozwoju, prawda?
Zrozumiały
O sukcesie modelu i całego projektu decyduje, czy wszyscy jego interesariusze rozumieją przestrzeń problemu. Model powinien stanowić podstawę porozumienia i bazę dla wszędobylskiego języka (ubiquitous language) służącego do komunikacji między wykonawcami systemu (ludźmi technicznymi), a ekspertami dziedzinowymi. W dzisiejszych czasach języki programowania takie, jak C#, są na tyle elastyczne, że pisanie kodu (modelu) tak, aby był zrozumiały dla nieprogramistów nie stanowi już większego problemu.
Wydajnie implementowalny
Mimo, iż model nie powinien być zależny od jakiejkolwiek konkretnej technologii, powinien jednak być (w jakiejś technologii) wydajnie implementowalny. Nie chcemy być zależni od NHibernate czy innego ORM-a, jednak akceptowanie faktu, że model będzie przechowywany trwale w jakiejś relacyjnej bazie danych za pośrednictwem jakiegoś ORM-a pozwala zoptymalizować wiele kwestii. Nie ma sensu udawać niezależności od wszystkich aspektów technologii.
Fractal structures
Jun 14th
For the last few days I was mainly pair-programming (pair-refactoring to be honest) some Silverlight UI code. I must confess I really rarely write UI code these days. Actually, I don’t remember the last time I’ve written a ASP.NET control from scratch. I did write some UI for DDDSample.Net but it was not production code after all.
MVVM
When I bumped into this Model-View-View Model (MVVM) stuff, I was a little confused. Then came the moment of enlightenment. I realized I saw all of this in totally different context – in loosely coupled system architecture. Look at this:
The blue elements on the right is MVVM stuff. It contains of three classic MVVM components and an additional one — an Event Aggregator. It is used for communication between MVVM triads. Application is organized in such a way, that multiple MVVM triads form vertical slices of functionality. These slices are loosely coupled so that they can be changed independently. Actually, when you value loose coupling much, you should avoid any code sharing between slices.
The orange elements are the building blocks of DDD-based distributed application. It receives commands via Services, then it processes them in a Domain Model which is backed by a Data Store. Finally, state changes are communicated to the outside world via a Message Bus.
How do slices communicate?
They use events and (rarely) messages. The name I chose for the diagram, the Event Aggregator, is a little bit misleading, because ViewModels should be able to both publish an event (broadcast to all interested receivers) and send direct message to a concrete receiver. The only shared thing between slices is the definition of messages (sounds like SOA, isn’t it?).
Again, how does it relate to system architecture?
I think the picture says it all. View is an analog of Service interface. View Model is an analog of Domain Model. In fact, one can say that View Model is the Domain Model in the context of user interface as it models the domain of particular UI case. The model is the place where all the information lives, a Data Store if you prefer. Finally, the Event Aggregator (with mentioned earlier unicast capabilities) is an analog of a Message Bus (such as NServiceBus).
How does it work?
Let’s look at the example. There are two slices, A and B. For the purpose of the example, let A be master view (a data grid and search) and B — a details view.
User selects the row on a grid. In response to that View A to generate a command and passes it (by means of data binding) to the View Model A. The View Model validates command, processes it (updates all required properties) and, as a result, it publishes an event “SelectionChanged”.
The Event Aggregator receives the event and searches for its subscribes. It finds View Model B so it forwards the event to it. During processing of event, View Model B requests all the data it needs from the Model B and updates its own properties using retrieved values. View B is automatically updates as it is data-bound to its View Model.
Summary
As you can see, good architected applications tend to have fractal structure. Similar forms can be discovered on various levels of detail. I value such architectures very high. The coherence makes them simple and elegant.
One more thing in case you didn’t noticed. There is a trend recently to focus architecture discussion not on what’s horizontal, but on what’s vertical. As we are (hopefully and at last) moving away from waterfallish processes, we start building applications slice by slice, not layer by layer. These slices do have to have some architecture but not as much design up front. The layers are much less important. I don’t even care if all the slices have the same layers — it can be impractical. What I want to have is the conscious process of choosing the layers of each slice so it can be implemented in the simplest possible way.
Why do I find Event Sourcing interesting?
Jun 7th
What makes Event Sourcing so interesting? I it is not the free, proven, audit trial, nor the possibility of reincarnating object in any particular state it had in the history. It is also not the great performance of add-only event store. It is testability.
Why Event Sourcing makes things testable? By splitting up logic responsible for two distinct things: calculating new state of the object and applying this state change. Normally, these two operations are mixed in one business logic method like this:
public class Customer
{
public void PlaceOrder(Product product, int quantity)
{
CheckProductAvailability();
CheckCustomerCreditLimit();
CheckSomethingElse();
decimal price = CalculateTotalPrice(product, quantity);
Order o = new Order(product, quantity, price);
_orders.Add(o);
}
//...
}
If you read this carefully, you will see that first 4 lines contain the business logic of state change calculation. The last 2 lines, on the other hand, contain no logic at all. Now, what’s the problem with this code’s testability?
The problem becomes visible, when I want to test how PlaceOrder behaves when adding fifth order. I have to call PlaceOrder four times to prepare state of the object. That is wrong, because my test code invokes a lot of logic during preparation phase. This means that a lot of things can go wrong and nobody likes tests which fail before the actual test code gets executed.
Let’s see if PlaceOrder can be corrected using SOLID principles. Does it have exactly one reason to change? It seems like it does. When order placement logic changes, the method changes, right? Let’s dig deeper. What about the last two lines? Do they change when the rest of method changes? Well, I don’t think so. Looks like they indeed do represent a different concern. So let’s refactor them to a separate method.
public class Customer
{
public void PlaceOrder(Product product, int quantity)
{
CheckProductAvailability();
CheckCustomerCreditLimit();
CheckSomethingElse();
decimal price = CalculateTotalPrice(product, quantity);
ApplyPlaceOrder(product, quantity, price);
}
private void ApplyPlaceOrder(Product product, int quantity, decimal price)
{
Order o = new Order(product, quantity, price);
_orders.Add(o);
}
//...
}
Now it looks better. At least from the have one reason to change principle. But I don’t see this improved testability at all. That is because although we split the responsibilities to separate methods, we left the methods tightly coupled to one another. Lets see what we can do about this.
public class Customer
{
public void PlaceOrder(Product product, int quantity)
{
CheckProductAvailability();
CheckCustomerCreditLimit();
CheckSomethingElse();
decimal price = CalculateTotalPrice(product, quantity);
Apply(new OrderPlacedEvent(product, quantity, price));
}
private void OnOrderPlaced(OrderPlacedEvent @event)
{
Order o = new Order(@event.Product, @event.Quantity, @event.Price);
_orders.Add(o);
}
//...
}
We introduced a concept of event as a way of encapsulating the state change we are about to apply. Now we can represent state changes by a series of events. The other thing we did was adding the Apply method. What is it? The Apply method is the API of Event Sourcing. It is a way of communicating hey, I, the aggregate root, want to apply the state change represented by this event! Apply invokes a method to apply state change. It can do this in a variety of ways: based on convention (like On… convention in this sample), an attribute or any other. The bottom line is, Apply applies the state change and it can be called externally, for example during tested state preparation.
[TestFixture]
public class CustomerTests
{
[Test]
public void When_placing_fifth_order()
{
Product product = new Product();
//Arrange
Customer c = new Customer();
c.Apply(new OrderPlacedEvent(product, 1, 10));
c.Apply(new OrderPlacedEvent(product, 1, 10));
c.Apply(new OrderPlacedEvent(product, 1, 10));
c.Apply(new OrderPlacedEvent(product, 1, 10));
//Act
c.PlaceOrder(product, 1);
//Assert...
}
}
You can see that we invoke no business logic during arrange phase. We only apply predefined, known to be right, state changes. The chance something bad will happen during this phase is minimal, as we certainly tested the OnOrderPlaced method in a different test, didn’t we?
This is what I call well tested code. PlaceOrder method is always tested in isolation, no matter if it is first or fifth order. One cannot overestimate the benefits of a testable codebase. If you happen to play (or work) with Domain-Driven Design, Event Sourcing can be beneficial for you. I see it as the missing piece of puzzle — the model is finally fully testable.
Ncqrs
May 30th
W ciągu ostatnich dwóch tygodni moją uwagę przykuł na dobre nowy framework open source — Ncqrs (witryna CodePlex Ncqrs znajduje się tutaj). Jak sugeruje nazwa, Ncqrs służy do budowy systemów w oparciu o wzorzec architektoniczny Command-Query Responsibility Separation (CQRS). To, czego nazwa nie mówi, to fakt, że Ncqrs narzuca pewną specyficzną implementację wspomnianego wzorca, a mianowicie tę opartą o technikę Event Sourcing. Na podstawie posta Grega Younga można by się czepiać, że nazwa Ncqrs nie jest zbyt trafna, ale odłóżmy kwestie nomenklatury na bok. Czym jest Ncqrs i jak to się stało, że mnie tak zafascynował?
Zasada działania
Ncqrs jest całościowym rozwiązaniem służącym do budowy systemów opartych o silny model domeny, którego stan jest przechowywany za pomocą strumienia zdarzeń. Poniższy diagram prezentuje workflow dla pojedynczego przypadku użycia w Ncqrs.
Komendy
Punktem wejścia do Ncqrs są komendy. Są to obiekty, który reprezentują żądania wykonania pewnej operacji na modelu domeny. Komendy są mapowane na operacje za pomocą rozbudowanego rozszerzalnego mechanizmu. Najprostsza implementacja mappera opiera się na dwóch atrybutach, które określają, czy dana komenda ma wykonywać metodę istniejącego obiektu (w nomenklaturze Ncqrs — korzenia agregatu), czy też tworzyć nowy obiekt. Properties komendy są mapowane (na podstawie nazwy) do parametrów wybranej metody lub konstruktora. Ostatecznie, (jeśli to konieczne) z magazynu danych podnoszony jest odpowiedni obiekt i wykonywana jest odpowiednia metoda (lub konstruktor).
Operacje biznesowe
Operacje i konstruktory obiektów są w Ncqrs wywoływane tylko za pośrednictwem komend. Ich jedynym zadaniem jest wykonanie logiki biznesowej. Nie mogą one bezpośrednio modyfikować stanu obiektu. Zamiast tego, dozwolonym mechanizm modyfikacji stanu jest zgłaszanie zdarzeń. Operacje biznesowe mogą także komunikować się ze światem zewnętrznym.
Stosowanie zdarzeń
Jak już wspomniałem na wstępie, zdarzenia są sposobem przechowywania stanu obiektów w Ncqrs. Operacja biznesowa może zgłosić jedno lub więcej zdarzeń. Dla każdego z nich framework wyszukuje odpowiedniej metody je przetwarzającej. Mechanizm ten jest oczywiście rozszerzalny, a out-of-the-box Ncqrs zapewnia dwa sposoby wiązania zdarzenia z metodą obiektu biznesowego przeznaczoną do jego obsługi: za pomocą konwencji oraz za pomocą atrybutów. Ncqrs wywołuje znalezioną metodę przekazując jej zgłoszone zdarzenie.
Przetwarzanie zdarzeń
Metoda przetwarzająca zdarzenie modyfikuje stan obiektu na podstawie danych przekazanych zdarzeniu. Tylko tyle i aż tyle. Metoda ta nie powinna zawierać żadnej logiki biznesowej (warunkowej), ani mieć jakichkolwiek skutków ubocznych (komunikacja z innymi systemami itp.).
Zapewne chcielibyście zapytać po co tyle komplikacji? Dlaczego operacja biznesowa nie może zmodyfikować stanu? Odpowiedź jest prosta. Ponieważ stan obiektów jest reprezentowany jako strumień zdarzeń przez nie wygenerowany, aby odtworzyć obiekt niezbędne jest stworzenie jego pustej instancji, a następnie przetworzenie (w kolejności!) wszystkich zapisanych zdarzeń — oczywiście za pomocą odpowiednich metod przetwarzających. Metody te są więc nie tylko stosowane do modyfikacji stanu podczas przetwarzania, ale także do odtwarzania tego stanu podczas podnoszenia obiektu z trwałego magazynu.
Dzięki takiemu podejściu systemy Event Sourcing (jak Ncqrs) zapewniają, za darmo, ślad audytowy, który ma gwarancję poprawności, ponieważ jest on wykorzystywany do budowy obiektów podczas normalnego działania systemu.
Publikowanie i denormalizacja zdarzeń
Jeśli wszystko do tej pory przebiegło prawidłowo, wszystkie zgłoszone zdarzenia są publikowane. Oczywiście, także w tym wypadku Ncqrs pozwala wymienić mechanizm publikacji zdarzeń. Domyślny wykorzystuje komunikację wewnątrz procesu, ale dostępny jest także taki, który wykorzystuje NServiceBus.
Publikowanie zdarzeń ma dwa cele. Po pierwsze, pozwala powiadomić zainteresowane systemy zewnętrzne o zmianach stanu naszego systemu. Polega to na eskalowaniu “lokalnych” (dotyczących naszego systemu) zdarzeń do statusu zdarzeń “globalnych” (mających znaczenia dla całego środowiska systemów). Stąd już tylko jeden krok do pełnej Event Driven Architecture (EDA).
Drugim celem publikowania zdarzeń jest tzw. denormalizacja, czyli aktualizacja podsystemu obsługi zapytań. Jaki podsystem? O co chodzi? Dokładny opis zagadnienia CQRS znajduej się tutaj. W tym miejscu wspomnę tylko, że systemy CQRS wykorzystują zwykle dwa osobne magazyny danych dla przetwarzania komend oraz do realizacji zapytań. Do synchronizacji tego drugiego magazynu danych wykorzystywane są właśnie denormalizatory zdarzeń. Proces denormalizacji polega na wykonaniu w bazie danych dla zapytań modyfikacji, które wynikają z opublikowanego zdarzenia. Skąd taka nazwa? Otóż zdarzenia stanowią znormalizowaną (pozbawioną redundancji) postać danych. W magazynie dla zapytań zaś, te same dane mogą mieć wiele reprezentacji, ponieważ nadrzędnym celem jest optymalizacja czasu realizacji zapytań.
Dlaczego to może działać?
Jest kilka powodów, które sprawiają, że (pozornie) szalona idea reprezentacji stanu obiektów jako ciągu zdarzeń może działać w praktyce. Oto kilka z nich:
- zapewnia darmowy, gwarantowany, ślad audytowy
- możliwość wykonywania tzw. snapshot’ów (czyli pełnych zrzutów zserializowanego obiektu biznesowego) co N zdarzeń. Dzięki temu odtwarzanie obiektu wymaga jedynie przetworzenia zdarzeń, które nastąpiły po ostatnim snapthot’cie
- w klasycznym DDD (z użyciem O/RM) podczas podnoszenia obiektu z bazy danych tak naprawdę pobierane jest wiele wierszy danych (np. za pomocą podzapytań i złączeń). W przypadku Event Sourcingu pobieranych jest kilka wierszy przechowujących zdarzenia. Oba podejścia mają więc podobną złożoność na poziomie bazy danych.
Zapraszam Was do zabawy z Ncqrs. Postaram się odpowiedzieć na wszystkie Wasze pytania.
Entity Framework 4 a NHibernate
May 25th
W minioną sobotę miałem przyjemność uczestniczyć w krakowskiej edycji Visual Studio Community Launch. Co prawda, jako jeden z organizatorów, nie jestem najlepszą osobą do obiektywnej oceny tego wydarzenia, ale moim zdaniem było super.
Podczas VSCL miałem okazję sprawdzić się także w roli prelegenta, prowadząc dwie piętnastominutowe mikroprezentacje dotyczące Entity Framework 4 oraz Windows Communication Foundation 4. Przykłady kodu dla obu prezentacji umieściłem na MSDN Code Gallery odpowiednio tutaj i tutaj. Zapewne duża część z Was nie była na konferencji, dlatego postanowiłem owe przykłady omówić tutaj, na blogu. Dziś – EF4.
W obu przypadkach starałem się wpleść prezentację nowych funkcji danej technologii w jakąś większą historię. Dla Entity Framework tą większą historią jest możliwość współdzielenia (części) modelu z aplikacją napisaną w NHibernate.
Design
Solution wygląda tak:
Znajdują się w nim dwie aplikacje: obsługa zamówień (Orders) oraz bank (Accounts). Obie aplikacje składają się z prostego programu demonstrującego działanie, modelu domeny (Model) oraz obsługi persystencji (DataAccess). Współdzielony fragment to Parties – projekt niezależny od technologii persystencji (bez referencji do NHibernate lub EF) zawierający implementację podmiotów (osób fizycznych i organizacji).
POCO
Jednym z celów nowej wersji Entity Framework było wsparcie dla budowy modeli POCO (Plain Old CLR Object). Dlaczego jest to takie ważne? Ponieważ właśnie obsługa POCO pozwala wykorzystywać w EF klasy, które nie były napisane konkretnie z myślą o nim oraz, patrząc z drugiej strony, pozwala wykorzystać klasy napisane specjalnie dla EF w innych kontekstach. POCO daje nam komfort braku zależności od frameworku ORM na poziomie modelu domeny – coś, czego nie sposób przecenić. Dla przykładu, klasa Party reprezentująca podmiot wygląda tak:
public class Party
{
public virtual int Id { get; protected set; }
public virtual Address Address { get; protected set; }
protected Party(Address address)
{
Address = address;
}
protected Party()
{
}
}
Lazy Loading
Leniwe ładowanie (lazy loading) jest kolejnym wielkim nieobecnym z pierwszej wersji EF, który doczekał się implementacji w wersji drugiej (czwartej). Jest ono jednak domyślnie wyłączone. Aby je włączyć, należy dodać do konstruktora kontekstu następującą linię:
ContextOptions.LazyLoadingEnabled = true;
Wszystko jednak ma swoją cenę. Ceną, którą płacimy za obsługę leniwego ładowania w EF jest konieczność „wirtualizacji” wszystkich property klas modelu. Jest to niezbędne, ponieważ EF w trakcie działania systemu generuje dynamicznie klasę dziedziczącą po naszej. Nie jest to jednak wielki problem, ponieważ NHibernate ma analogiczne wymaganie. Możemy więc spokojnie przywyknąć do myśli, że standardem dla ORM jest konieczność deklarowania wirtualnych property.
Value Objects
Value Objects są nazywane w EF „Complex Type”. Ot, taka fanaberia ludzi z Microsoft. Complex Type w EF może być modyfikowalny. Ja osobiście jednak odradzałbym to ze wszystkich. Niemodyfikowalność Value Object jest bardzo ważną cechą, m.in. ułatwiającą testowanie.
Ponieważ dla Complex Type EF nie prowadzi change tracking’u (sprawdzenie, czy należy zaktualizować bazę wykonywane jest za pomocą porównania wartości aktualnych i pobranych), nie ma sensu wirtualizacja properties CT. Znowu, jest to zgodne z tym, jak ja pracuje z NHibernate. Moje Value Objects muszą być jak najbardziej niezależne kontekstu bazodanowego. Jedyne na co się mogę (i niestety muszę) zgodzić to pusty konstruktor z widocznością protected. Zarówno EF, jaki NHibernate go wymagają. Efektem tego zestawu wymagań jest klasa Address będąca Value Objectem reprezentującym adres podmiotu:
public class Address
{
public string Street { get; private set; }
public string BuildingNumber { get; private set; }
public string City { get; private set; }
public string Country { get; private set; }
protected Address()
{
}
public Address(string street, string buildingNumber, string city, string country)
{
Street = street;
BuildingNumber = buildingNumber;
City = city;
Country = country;
}
}
Enkapsulacja
Enkapsulacja to zawsze dobra rzecz. Nie inaczej jest w wypadku modelu domeny. Upublicznianie getterów (a broń Boże setterów) właściwości jest prostą drogą do degradacji naszego modelu do roli prostych struktur danych. A przecież nie o to nam wszystkim chodzi. Z tego powodu bardzo się zmartwiłem po przeczytaniu na MSDN opisu wymagań dla POCO w EF. Wynika z niego (chyba, że ja źle rozumiem), że aby leniwe ładowanie działało, wszystkie właściwości klasy muszą być „public virtual”.
Na szczęście przeglądając sieć natknąłem się na wzmiankę, iż wystarczy „protected virtual”. Postanowiłem sprawdzić. Jakaż była moja radość, gdy okazało się, że wszystko działa. W kontekście enkapsulacji EF4 ma analogiczne wymagania, co NHibernate. Yupi:-)
Klasa Account jest świetnym przykładem enkapsulacji dla kolekcji: lista operacji związanych z kontem nie może być modyfikowana bezpośrednio, ale jedynie poprzez wywołania Credit lub Debit:
public class Account
{
//...
public decimal Balance { get; protected set; }
protected virtual IList<Operation> Operations { get; set; }
//...
public virtual void Debit(decimal amount, string title)
{
if (Balance - amount < 0)
{
throw new InvalidOperationException();
}
var op = new Operation(-amount, title);
Operations.Add(op);
Balance -= amount;
}
public virtual void Credit(decimal amount, string title)
{
var op = new Operation(amount, title);
Operations.Add(op);
Balance += amount;
}
}
Podsumowanie
Druga edycja Entity Framework jest o niebo lepsza od poprzedniej. Właściwie mogę powiedzieć z czystym sumieniem, że jest nawet używalna w kontekście Domain-Driven Design. Cieszy mnie to niezmiernie, bo lubię mieć wybór. Pluralizm to dobra rzecz. Czy rozważam przesiadkę z NHibernate na EF? Na pewno nie w tej wersji. Przewaga NH w przypadku bardziej skomplikowanych kwestii jest jeszcze zbyt duża. Z drugiej strony nie potrzebuję (i nie będę potrzebował) wszystkich tych kwestii związanych z obsługą architektur n-tier, w które Microsoft pakuje tyle pary.
Jeśli jednak EF będzie się rozwijać w tak szybkim tempie, jest wysoce prawdopodobne, że wersja 6.0 będzie stanowić groźną konkurencje dla NHibernate także w kontekście DDD.









