Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
O regionach raz jeszcze
Dziś, zupełnie przypadkiem, dowiedziałem się ciekawej rzeczy. Otóż nie ja pierwszy obwiniam regiony o bycie źródłem wszelkiego zła. Spróbujcie poguglać w poszukiwaniu "region blocks in code are the equivalent of putting down the toilet lid but not actually flushing".

Miłej lektury.

opublikowano 10 listopada 2009 19:36 przez simon | 1 komentarzy

Filed under:

Kontrowersyjny esej o kodzie czytelnym, część 4: komentarze
Komentarze w kodzie metod pomagają nam zrozumieć, co miała na myśli osoba implementująca algorytm realizowany przez daną metodę: dlaczego ten if wygląda tak i po co jest ta pętla. Komentarze pozwalają też na oznaczenie etykietami pewnych logicznych fragmentów metody, które stanowią spójną całość. Takie fragmenty są też często zamykane w regiony.

Zarówno komentarze, jak i regiony, wewnątrz metod są znakiem, że kod potrzebuje refaktoryzacji. Sam z siebie jest nieczytelny, dlatego my, mądrzy deweloperzy, szukamy innych sposobów na uczynienie go czytelnym – stosujemy więc komentarze i regiony. Zamiast tego powinniśmy zastosować refaktoryzację polegającą na wydzieleniu metody (i nadaniu jej nazwy, która będzie na tyle ekspresyjna, aby wyjaśniała cel logiki znajdującej się wewnątrz). Pod koniec pierwszej dekady XXI wieku nie musimy się już martwić kosztem wywołania metody.

Następnym razem, gdy zobaczysz w kodzie metody komentarz/region zastanów się, czy jest on tam niezbędny. Czy nie lepiej wydzielić metodę?

opublikowano 3 listopada 2009 18:10 przez simon | 14 komentarzy

Filed under:

Kontrowersyjny esej o kodzie czytelnym, część 3: język w kodzie
Wybierz jeden język nazewnictwa elementów kodu w projekcie lub zespole.

Całość kodu w ramach jednego projektu lub tworzonego przez jeden zespół powinna stosować jeden i ten sam język nazewnictwa elementów kodu (zmiennych, klas itp.). Dlaczego? Aby osoba czytająca kod, nawigując od klasy do klasy, nie musiała przestawiać swojego sposobu myślenia. Jak wszyscy programiści doskonale wiedzą, przełączanie kontekstu jest kosztowne. Należy go więc unikać.

Dlaczego zakres tej "unifikacji" języka to projekt lub zespół? Dlaczego nie cały dział lub firma? Według mnie byłby to przerost formy nad treścią. Tak wysoki poziom standaryzacji jest trudny do osiągnięcia organizacyjnie i kosztowny. Nie przynosi jednak (oczekiwanych?) korzyści, ponieważ bardzo rzadko mamy do czynienia z przełączaniem się między klasami związanymi z różnymi projektami lub zespołami podczas jednej "sesji pracy". Ciekawie pisze na temat standaryzacji i jej zakresu Paweł.

Tradycyjnie językiem nazewnictwa jest język angielski. Chciałbym jednak, abyś  przynajmniej rozważył możliwość zastosowania innego języka, zamiast „na ślepo” przyjmować angielski. Być może system, który budujesz jest skomplikowany i nazwanie elementów kodu tak, aby były rozumiane przez klienta pomoże Ci w zgraniu analizy i implementacji? Czy na pewno chcesz przechodzić przez etap tłumaczenia dla każdego elementu systemu? Czy na pewno wiesz w jaki sposób dobrze przetłumaczyć na angielski nazwy bytów z domeny problemu? I czy to właściwie w czymś pomoże? Nie oszukujmy się, zdecydowana większość programistów bardzo kiepsko zna angielski.

PS. C# jest zdefiniowany nad UTFem i nie sprawia mu problemu stosowanie "ogonków" w nazwach klas, zmiennych itp.

opublikowano 27 października 2009 18:33 przez simon | 5 komentarzy

Filed under:

Kontrowersyjny esej o kodzie czytelnym, część 2: VAR
Słowo kluczowe var jest sposobem na uniknięcie długich nazw typów podczas deklarowania zmiennych lokalnych. Dzięki niemu unikamy dwukrotnej specyfikacji typu, co może być przydatne w np. wypadku deklarowania zmiennej typu Dictionary<string, Dictionary<string, int>>.

Czy długie nazwy typów są naprawdę nieuniknione? Czy słownik słowników to dobry typ dla zmiennej lokalnej? Jeśli deklarujemy zmienną będącą złożeniem dwóch kolekcji, potrzebujemy umieścić gdzieś logikę odpowiedzialną za manipulowanie nią. Zwykle robimy to w jakichś prywatnych metodach. Zamiast tego, stwórzmy nowy typ opakowujący nasze skomplikowane złożenie i nadajmy mu prostą nazwę – na tyle krótką, aby dwukrotne jej umieszczenie w deklaracji zmiennej nie było bolesne.

Jeśli jednak jesteśmy fanami var – OK. Jeśli takie są standardy kodowania w Twoim zespole – OK. Pamiętaj tylko o jednym: stosowanie var przy deklaracji zmiennej której wartość początkowa jest wynikiem wywołania metody bardzo zmniejsza czytelność kodu. Skąd ktoś ma wiedzieć jakiego typu jest zmienna result, jeśli jej deklaracja wygląda tak: var result = GetSomethingFromSomePlace()? Tu dochodzimy do jednej z pozytywnych cech słowa kluczowego var. Jest nią według mnie bezlitosne eksponowanie problemu nic nie mówiących nazwy zmiennych. O ile moje dywagacje na temat var są wyrazem moich osobistych subiektywnych odczuć, o tyle plaga bezmyślnego nazywania elementów kodu jest bezdysusyjnie zła i jako taka powinna być tępiona z całą stanowczością.

opublikowano 22 października 2009 13:41 przez simon | 16 komentarzy

Filed under:

Kontrowersyjny esej o kodzie czytelnym, część 0
Pomny poprzednich zarzutów o publikowanie prawd objawionych chciałbym wyjaśnić kilka kwestii związanych z aktualną serią postów. Po pierwsze, nie narzucam nikomu moich poglądów. Publikuję ja, ponieważ są moje i ja, w tym momencie, w nie wierzę. Nie gwarantuje, że za dwa tygodnie nie dojdę do wniosku, że są błędne. Moje posty oddają mój aktualny stan wiedzy i zrozumienia. Nie twierdzę także, że moje poglądy są tymi właściwymi. Uważam, że każdy powinien mieć swoje i mieć argumenty, aby móc swoich poglądów bronić. Aktualna seria notek ma pobudzić Was do przemyślenia raz jeszcze Waszych nawyków związanych z tworzeniem oprogramowania. Być może część z nich straciła już nieco na aktualności i można z nich zrezygnować?

opublikowano 22 października 2009 13:29 przez simon | 0 komentarzy

Filed under:

Kontrowersyjny esej o kodzie czytelnym, część 1: Regiony
Regiony są sposobem na zwiększenie czytelności kodu poprzez logiczne pogrupowanie jego elementów w łatwe do ukrycia grupy. Dzięki nim możemy w prosty sposób przeglądać tylko wybrane elementy klasy.

Czy naprawdę jest to dla nas ułatwienie? Czy są takie przypadki, kiedy nie interesuje nas całość kodu klasy? Jeśli tak, być może oznacza to, że klasa ma za dużo odpowiedzialności. Jeśli klasa ma kilka logicznych fragmentów, które chcielibyśmy zamknąć w regiony, to tak naprawdę klasa może być podzielona na kilka mniejszych, które mniej-więcej odpowiadają potencjalnym regionom.

Klasa powinna mieć dokładnie jedną odpowiedzialność, a kod realizujący tę odpowiedzialność powinien być na tyle krótki, aby zamykanie go w regiony było zbędne.

Zamiast regionalizować – refaktoryzuj!

opublikowano 21 października 2009 18:22 przez simon | 5 komentarzy

Filed under:

Encje
Encje są prawdopodobnie najważniejszymi elementami modelu domeny. Reprezentują najistotniejsze obiekty domeny problemu, czyli te, które mają własną tożsamość.

Dwie encje o różnych identyfikatorach są różnymi obiektami. Tak przynajmniej można wyczytać w mądrych książkach. Jak to się jednak ma do praktyki?

W DDDSample porównywanie encji zrealizowane jest za pośrednictwem specjalnego interfejsu posiadającego jedną metodę sameIdentityAs(T other). Wszystkie klasy reprezentujące encje implementują ten interfejs porównując klucz biznesowe.

Dodatkowo wszystkie one implementują także metody equals i getHashCode na bazie zawieranych danych.

W DDDSample.Net postanowiliśmy przyjąć odmienną koncepcję. Polegamy na ORM-ie i jego implementacji Identity Map w kwestii zapewnienia, że dwie instancje encji o tym samym kluczu są w rzeczywistości jednym obiektem na stercie. Dzięki temu możemy korzystać z domyślnych implementacji Equals i GetHashCode opartych o fizyczne adresy w pamięci.

Więcej na ten temat możecie poczytać na dddsamplenet.blogspot.com

opublikowano 17 października 2009 07:52 przez simon | 3 komentarzy

Application Events vs Domain Events
Bardzo szybko przywykłem do idei "domain events" Udiego i nie spodziewałem się napotkania w kodzie DDDSample innego rozwiązania. Jakież było więc moje zdziwienie, gdy zdarzenia (z natury) domenowe odnalazłem nie na poziomie modelu domeny, ale na poziomie warstwy aplikacyjnej. Postanowiłem przejrzeć się bliżej temu tematowi (zamiast na ślepo kopiować Javowe rozwiązanie).

Wnioski mam dwa. Po pierwsze drobnoziarnista struktura pakietów Javy sprawia, że warstwa aplikacji jest wolna od kodu związanego z UI lub komunikatami (tym zajmuje się osobna warstwa fasady), więc umieszczanie tam zdarzeń nie jest dużym naruszeniem enkapsulacji logiki domenowej. Co innego w .NET, kiedy zwykle obie odpowiedzialności -- logiki i jej fasady -- realizowane są przez jedno assembly (i być może nawet jedną klasę).

Po drugie, umieszczenie zdarzeń na poziomie warstwy aplikacji zapobiega konfliktowi nazw "domain event"-ów Udiego ze zdarzeniami-elementami modelu domeny (niemodyfikowalnymi encjami).

Ostatecznie postanowiłem zmodyfikować nieco oryginalny projekt, aby móc użyć koncepcji "domain event" w DDDSample.Net.

Więcej na ten temat możecie poczytać na dddsamplenet.blogspot.com (po angielsku).

opublikowano 8 października 2009 17:53 przez simon | 12 komentarzy

DDDSample.Net żyje!
Z ogromną przyjemnością śpieszę donieść, iż projekt DDDSample.Net - .NET-owy klon projektu DDDSample - właśnie doczekał się pierwszej wersji. Wraz z projektem powstał także stosowny blog, na którym publikowane będą informacje dotyczące postępu prac oraz posty komentujące decyzje projektowe, które zostały podjęte przez autorów Java-owego oryginału.

Zachęcam wszystkich do ściągnięcia wersji 0.1 DDDSample.Net i pokeksperymentowania z Domain-Driven Design. Nie będziecie żałować!

opublikowano 5 października 2009 21:39 przez simon | 1 komentarzy

[EN] Execution-Definition Separation
Extrapolating CQS to the requirements of the latest system I've been working on I found useful to define a term Execution-Definition Separation. As I think this approach can be useful many more cases, I will write a little bit about the idea.

Briefly, CQS approach to architecture is about defining separate models (and probably their backing databases) for commands and queries (you can find more about this approach here and here). This approach (like all good DDD practices) assumes that new objects (aggregate roots) do not come out of thin air, but instead are created using methods of other objects. My case was a bit different. The majority of my domain objects are created using GUI and are quite static. They don't change frequently and are not a subject for transactional processing. Their relations and methods define the business rules of processing. And there are other objects in the domain which are a subject of this processing: orders and transactions.

The first group of objects (let's call them 'business definition objects') were a mystery to me. They are not a domain object according to classical definition. From one perspective I had to allow them to be crated, edited, linked with neighbours using the UI - simple CRUD operations, not very well alligned with DDD principles. From the other I would see them as a fairly static structure with methods to execute specific business rules. It looks like an inverted CQS model:

changes are made to the model exposed by the UI (query or reporting model) and are asynchronously applied to the model which takes part in transaction processing (command model)

I decided to follow that idea and give it a name - Execution-Definition Separation. I designed a definition model to be used be ASP.NET GUI: binds to controls quite well, is simple, has all the data (including fancy names and -- probably localized -- descriptions) mapped as properties.



Due to it's simplicity I decided not to use domain events as a way of pushing out change notifications. Instead, I use Command pattern to encapsulate request for changes to this Definition model. These commands are executed against Definition model (and database) and then serialized and asynchronously published to all interested parties (including the Execution model host process). Commands are then deserialized in the Execution model process and executed against its model (and database). The technical trick here is that WCF DataContractSerializer which I use can serialize an instance and deserialize is as instance of another type as far as data contracts are compatible.



The execution model has no public properties, only the business methods and methods needed to apply changes to its structure. Additionally, this model can be cached using NHibernate second level cache to improve performance. Execution model is simple and not polluted by GUI related code like validation and CRUD support: only pure logic.



I find CSQ principles very interesting and I will definitively continue experimenting with them in my future systems.

opublikowano 22 września 2009 16:53 przez simon | 0 komentarzy

Mityczne 100% pokrycia
W środowisku deweloperskim wciąż żywy jest mit 100% pokrycia kodu testami jednostkowymi. Co gorsza, mit ten ma się równie dobrze (a może nawet lepiej?) wśród decydentów (kierowników, dyrektorów itp.).

Celem poniższej notki jest pokazanie, jak bardzo naiwne jest podejście "100% pokrycia".

Popatrzmy na następujący trywialny kod:

    1 public class SomeBusinessClass

    2 {

    3    private int m_valueToCompare;

    4    public int ValueToCompare

    5    {

    6       get { return m_valueToCompare; }

    7       set { m_valueToCompare = value; }

    8    }

    9 

   10    public bool IsGreaterThan(int value)

   11    {

   12       if (value >= ValueToCompare)

   13       {

   14          //Skomplikowana logika

   15          return true;

   16       }

   17       else

   18       {

   19          //Skomplikowana logika

   20          return false;

   21       }

   22    }

   23 }


Nie zwracajcie, proszę, uwagi na postać tego if-a. Wiem, że można to napisać prościej, ale tak jest bardziej demonstracyjnie. Kod zawiera oczywisty błąd - ktoś pomylił operatory w porównaniu. Oto jak inny ktoś, używając szablonu testowego VisualStudio, mógłby napisać testy:

    1 [TestClass()]

    2 public class DumbSomeBusinessClassTest

    3 {

    4    [TestMethod()]

    5    public void ValueToCompareTest()

    6    {

    7       const int expected = 5;

    8       SomeBusinessClass target = new SomeBusinessClass();        

    9 

   10       target.ValueToCompare = expected;

   11       int actual = target.ValueToCompare;

   12 

   13       Assert.AreEqual(expected, actual);        

   14    }

   15 

   16    [TestMethod()]

   17    public void IsGreaterThanTest()

   18    {

   19       const bool expected1 = false;

   20       const int value1 = 2;

   21 

   22       const bool expected2 = true;

   23       const int value2 = 6;         

   24 

   25       SomeBusinessClass target = new SomeBusinessClass();

   26       target.ValueToCompare = 4;        

   27 

   28       bool actual1 = target.IsGreaterThan(value1);

   29       bool actual2 = target.IsGreaterThan(value2);

   30 

   31       Assert.AreEqual(expected1, actual1);

   32       Assert.AreEqual(expected2, actual2);     

   33    }

   34 }


A następnie uruchomić:



i cieszyć się wspaniałymi statystykami (100% pokrycia):



Niestety wszystko to na nic, bo błąd i tak został niewykryty. Zamiast ślepo dążyć do ideału stu procent, lepiej pomyśleć, co tak naprawdę jest ważne w naszej klasie. Rezultatem takiej sesji myślenia jest klasa testowa napisana zgodnie z reklamowanym tutaj schematem Method_State_Result:

    1 [TestClass()]

    2 public class SmartSomeBusinessClassTest

    3 {     

    4    [TestMethod]

    5    public void IsGreaterThan_ValueBelowThreshold_FalseReturned()

    6    {                 

    7       SomeBusinessClass target = new SomeBusinessClass();

    8       target.ValueToCompare = 4;

    9 

   10       bool actual = target.IsGreaterThan(2);        

   11 

   12       Assert.AreEqual(false, actual);        

   13    }

   14    [TestMethod]

   15    public void IsGreaterThan_ValueEqualsThreshold_FalseReturned()

   16    {

   17       SomeBusinessClass target = new SomeBusinessClass();

   18       target.ValueToCompare = 4;

   19 

   20       bool actual = target.IsGreaterThan(4);

   21 

   22       Assert.AreEqual(false, actual);

   23    }

   24    [TestMethod]

   25    public void IsGreaterThan_ValueAboveThreshold_FalseReturned()

   26    {

   27       SomeBusinessClass target = new SomeBusinessClass();

   28       target.ValueToCompare = 4;

   29 

   30       bool actual = target.IsGreaterThan(6);

   31 

   32       Assert.AreEqual(true, actual);

   33    }

   34 }


Co ciekawe, klasa ta ma dokładnie tyle samo linii, co jej "głupia" wersja. Tym razem uzyskujemy "jedynie" około (w zależności od metryki) 90 procent pokrycia. Ale za to:



Jest jeszcze jeden bardzo ważny negatywny aspekt głupiego dążenia do 100% pokrycia. Otóż daje ono autorowi fałszywe poczucie pewności, iż przetestowana "idealnie" klasa jest bezbłędna. Przekonanie, że pełne pokrycie testami gwarantuje bezbłędność jest tak silne wśród programistów, że wręcz powoduje wyłączenie zdrowego rozsądku: Przecież mam 100% pokrycia! To na pewno nie mój błąd!

Nie dajmy się zwieść mitom. Kierujmy się zdrowym rozsądkiem. Mądre 85% jest lepsze, niż naiwne 100%.

opublikowano 18 września 2009 14:14 przez simon | 8 komentarzy

Filed under:

DTO vs CQS
Dzisiejsza notka jest wolnym tłumaczeniem jednej z poprzednich, którą opublikowałem in English. Ilość informacji na ten temat w polskim internecie jest tak znikoma, iż wydaje mi się, że każda notka jest ważna.

Pomijając historię, jak udało mi się dość do moich wniosków, chciałbym zaprezentować rzetelne porównanie podejść DTO oraz CQS w kontekście udostępniania informacji na potrzeby interfejsu użytkownika.

DTO


Wzorzec DTO polega na zdefiniowaniu, dla każdego obiektu Modelu Domeny, obiektu Data Transfer, który posiada wszystkie dane obiektu Modelu, ale nie posiada zachowania. Jest to kontener na dane, który może być przekazywany między warstwami.

[Edit]
Jak słusznie zauważył Wojciech Gebczyk, powyższa definicja to jedna z form wzorca DTO. Inna, zupełnie legalną, formą jest jeden  (lub dwa: input oraz output) obiekt DTO dla każdej operacji udostępnianej przez fasadę modelu. Poniższe rozważanie nie dotyczy obiektów DTO przeznaczonych do przenoszenia argumentów wejściowych operacji, a jedynie tych, które służą do transportu wyników zapytań.
[/Edit]

DTO są zwykle tworzone za pomocą wzorca DTO assembler, czyli specjalizowanej klasy, która przepisuje dane z obiektu Modelu do obiektu DT. Assembler może być zastąpiony tzw. object/object mapperem (np. AutoMapper) analogicznie, jak ręcznie napisany DAL może być zastąpiony O/RM-em.

Poniższy rysunek przedstawia schemat interakcji z modelem domeny, eksponowanym za pośrednictwem aplikacji WWW, składający się z dwóch faz: pobranie danych na GUI (pierwsze żądanie) oraz wykonanie operacji biznesowej (drugie żądanie). Wyraźnie widoczne są trzy fazy transformacji danych.


Legenda:
  • Strzałka z pustym grotem - przepływ sterowania
  • Strzałka z pełnym grotem - przepływ danych
  • Bloczek niebieski - transformacja danych
  • Bloczek źółty/pomatańczowy - relacyjna baza danych
  • MD / MDZ / MDK - model domeny / model domeny dla zapytań / model domeny dla komend

CQS


Na temat definiowania CQS w kontekście architektury pisał sporo Greg Young, więc nie będę odkrywał koła na nowo. W naszym kontekście istotą CQS jest wydzielenie dwóch modeli: modelu dla komend oraz modelu dla zapytań. Model dla komend realizuje logikę biznesową. Jest, z grubsza, taki sam, jak ogólny model w wypadku użycia DTO. Obiekty modelu dla komend nie muszą jednak eksponować publicznie swoich danych.

Model dla zapytań zawiera klasy reprezentujące obiekty domeny w kontekście zapytań. Są to, podobnie jak w wypadku DTO, klasy pozbawione zachowania. Różnica polega na tym, że obiekty tych klas nie są one budowane na podstawie obiektów modelu dla komend, ale bezpośrednio na podstawie bazy danych (np. w wykorzystaniem biblioteki OR\M).

Poniższy rysunek przedstawia analogiczny (do tego z DTO) schemat interakcji. Tym razem widoczne są tylko dwie transformacje. Trzecia, ukryta, wykonywana jest na etapie synchronizacji danych.


Porównanie


Oba podejścia wymagają utrzymywania dwóch równoległych zestawów klas opisujących obiekty domeny. Oba również wymagają stworzenia logiki tworzącej obiekty DTO lub obiekty modelu dla zapytań. Ilość kodu wymagana do realizacji obu rozwiązań jest więc porównywalna.

Na tym podobieństwa się kończą. Wszelkie inne kwestie przemawiają na korzyść CQS:
  • CQS lepiej przygotowuje aplikację do skalowania, ponieważ umożliwia przeźroczyste dla kodu rozdzielenie baz danych stojących za modelami i realizację replikacji z bazy transakcyjnej (model dla komend) do bazy raportowej (model dla zapytań)
  • CQS (przy założeniu rozdzielenia baz) pozwala zoptymalizować obie bazy pod kątem operacji na nich wykonywanych (insert/update vs select)
  • CQS daje większą swobodę w tworzeniu GUI, ponieważ obiekty modelu dla zapytań mogą znacznie różnić się od obiektów modelu dla komend.
  • CQS skutkuje o wiele czystszym modelem logiki biznesowej, ponieważ klasy modelu dla komend nie są "zaśmiecone" kodem spełniającym wymagania warstwy prezentacji

Podsumowując, CSQ kosztuje tyle samo, a daje znacznie większe możliwości. Dlaczego miałbym więc wracać do DTO?

opublikowano 17 września 2009 09:51 przez simon | 6 komentarzy

To tylko kwestia nazewnicza
Pracuję teraz nad pewnym systemem. System ten składa się z dwóch części. Jedna z nich to interfejs dla administratora merytorycznego. Interfejs ten pozwala konfigurować sposób, w jaki druga - transakcyjna - część systemu wykonuje swoją pracę. Obie części systemu posiadają własne modele domeny. W części konfiguracyjnej model zorientowany jest na współpracę z GUI. W części transakcyjnej zaś - na przetwarzanie zleceń. Zmiany dokonane w modelu konfiguracyjnym są przesyłane do modelu transakcyjnego, gdzie dokonywane są odpowiednie aktualizacje.



Jak wynika z powyższego opisu, nasz ubiquitous language (naprawdę nie wiem jak do dobrze przetłumaczyć) operuje pojęciami: konfiguracja, część konfiguracyjna, część transakcyjna. Wydawało się to rozsądne. Dopóki nie przyszło nam zdecydować w jaki sposób dane konfiguracyjne mają być synchronizowane.

Dopiero po fakcie zrozumiałem, jak duży ładunek (niepotrzbnej) treści wiąże się z niewinnym słowem "konfiguracja". Sugeruje ono odbiznesowienie omawianego zagadnienia; sprowadzenie go do kawałka "infrastruktury konfiguracyjnej". Pcha niezmordowanie ku generyczności rozwiązania problemu: dodanie informacji "aktywny od", "aktywny do", "modyfikujący". Kulminacją jest zaś stwierdzenie, że dany obiekt to "obiekt konfiguracyjny". Ogołaca to encję z wszelkiego biznesowego sensu, czyniąc z niej prosty kontener na dane, dla którego najważniesze są wspomniane "daty obowiązywania". Nie ma w tym procesie niczyjej złej woli. Jest tylko naturalna konsekwencja złego doboru nazwy dla dobrej, przecież, koncepcji.

Zamiast tego powinniśmy powiedzieć, iż system dzieli się na część służącą do definiowania reguł biznesowych oraz część wykonywania tych reguł. Niby to samo, ale poprzez takie stwierdzenie podkreślamy, iż to, co dzieje się w części GUI administratora, to działalność biznesowa. A więc obiekty są także biznesowe. Nie wrzucamy ich do jegnego wspólnego wora, ale traktujemy indywidalnie, próbując zawrzeć w ich kodzie reguły, jakimi posługuje się biznes, a nie takie, jakimi posługuję się infrastruktura synchronizacji konfiguracji.

opublikowano 14 września 2009 14:32 przez simon | 4 komentarzy

[PL] Baza danych dla każdego dewelopera!
Napotkałem ostatnio interesujący problem. Szczerze mówiąc problem znany mi był już od dawna, ale w przypadku mojego aktualnego systemu stał się on na tyle palący, że zmuszony zostałem podjąć jakieś środki zaradcze.

Jak zorganizować pracę deweloperom w kontekście serwera bazodanowego?

Najbardziej popularnym w mojej firmie wariantem jest utrzymywanie jednej instancja bazy danych dla całego projektu. Wszyscy deweloperzy korzystają z tej jednej bazy. Konfiguracja (pliki app/web.config) przechowywane w systemie kontroli wersji posiadają odwołania bezpośrednio do tej bazy. Podczas wystawiania wersji na środowisko testowe lub produkcyjne pliki te są podmieniane na specyficzne dla danego środowiska.

Rozwiązanie takie nie jest doskonałe, ponieważ podczas intensywnych prac deweloperzy mogą niszczyć sobie nawzajem dane, na których testują swój kod. Powyżej pewnej krytycznej liczby osób w projekcie, efektywność takiego rozwiązania spada drastycznie. Kolejnym minusem jest problem z uruchamianiem automatycznych testów bazodanowych: jeśli dwóch deweloperów będzie próbować zrobić to w tej samej chwili efekt może być nieprzewidywalny. Wystarczy kilka takich "wpadek", aby skutecznie zniechęcić zespół do uruchamiania testów...

W moim wypadku wszystkie wymienione powyżej problemy byłyby do zniesienia. Zadecydowała dopiero specyfika projektu: część danych jest synchronizowana pomiędzy dwoma aplikacjami wchodzącymi w skład rozwiązania. Synchronizacja ta odbywa się za pośrednictwem NServiceBus oraz kolejek MSMQ. Istnieje przez to realna groźba całkowitego rozsynchronizowania danych, ponieważ kolejki znajduja się na maszynach deweloperskich, a baza jest współdzielona.

Idealnie byłoby, aby każdy deweloper dysponował kompletnym środowiskiem: zarówno kolejkami, jak i bazą danych. Problem pojawia się w momencie definiowania łańcuchów połączeń do bazy danych. Framework sam z siebie nie udostępnia żadnego gotowego mechanizmu rozwiązującego go. Do dyspozycji mamy jedynie półśrodki: zewnętrzne pliki konfiguracyjne (atrybut conigSource) oraz EntLib-owe pliki "łatek".

Ja zdecydowałem się na użycie tego pierwszego mechanizmu. Potrzebowałem jedynie jakoś zarządzać tymi zewnętrznymi plikami. Moje rozwiązanie zakłada, że każde solution ma wspólną konfigurację bazodanową. Jest ona zlokalizowana w folderze Configuration. Folder ten zawiera podfoldery dla każdego użytkownika - dewelopera. W mojej firmie korzystamy z domeny AD, więc w Configuration znajdują się katalogi o nazwach takich jak loginy użytkowników, np. Configuration\Szymon.Pobiega. W takim folderze zlokalizowane są pliki konfiguracyjne specyficzne dla mojego środowiska. W tym momencie jest to jeden plik - Database.config - zawierający connection string do instancji SQL znajdującej się na mojej maszynie.

Zastanawiacie się pewnie po co to wszystko? No więc sednem mechanizmu jest malutki programik (możecie go pobrać stąd), który potrafi kopiować plik konfiguracyjny związany ze środowiskiem zalogowanego użytkownika z głównego "repozytorium" (folder Configuration) do katalogu docelowego build-a danego projektu. Programik włączany jest w proces budowanie za pomocą zdarzenia PostBuild. Przykładowe konfiguracje wyglądają następująco:

E:\FoldeGłównyTeamProjectu\Common\ConfigCopier.exe Database.config $(SolutionDir)Configuration $(ProjectDir)\bin
E:\FoldeGłównyTeamProjectu\Common\ConfigCopier.exe Database.config $(SolutionDir)Configuration $(TargetDir)

Argumenty ConfigCopier-a to:
  1. nazwa pliku konfiguracyjnego
  2. ścieżka do głównego repozytorium
  3. ścieżka docelowa
Pierwsza z konfiguracji dotyczy aplikacji ASP.NET, druga "zwykłego" projektu. Zwróćcie uwagę na delikatną różnicę w określeniu lokalizacji docelowej. Sekcje connectionStrings mają postać, odpowiednio:

<connectionStrings configSource="bin\Database.config"/>

<connectionStrings configSource="Database.config"/>


W przypadku aplikacji ASP.NET plik kopiowany jest do katalogu bin, aby zapobiec przypadkowemu jego włączeniu do systemu kontroli wersji. Implementacja rozwiązania zajęała mi mniej niż godzine. Po tym czasie mogłem już się cieszyć odizolowanym środowiskiem zarówno do testów jednostkowych, jak i do normalnych moich deweloperskich zadań. Nie muszę dodawać, że ustawienia bazodanowe to tylko przykład. W ten sam sposób można spersonalizować dowolną sekcję konfiguracyjną. Póki co jest to dopiero prototyp. Docelowo chciałbym, aby rozwiązanie zostało wzbogacone o możliwość definiowania konfiguracji nie tylko per deweloper, ale także nazwanych, takich jak "staging" czy "production". Byłoby to wspaniałe uzupełnienie naszego procesu Continous Integration.

opublikowano 10 września 2009 22:56 przez simon | 1 komentarzy

Filed under:

[EN] Is DTO an excuse?
This thought came into my mind when I was returning home from work. It all started with this old dilemma - how should I transform my domain objects into DTO? Maybe I should use a tool, like AutoMapper? Or maybe DTOs should build themselves from domain objects? Maybe DTO assembler pattern is the way to go? There are many possibilities and all of them are answers to my question. And also all of them are correct. Only the question is wrong. How could I be so blinded?

I should ask myself a different question: How would I design my system so that:
  1. business rules are implemented by a domain model,
  2. the domain model is accessed only via application service layer,
  3. GUI allows users to search and view properties of domain objects
  4. there is no unnecessary code duplication
As you see, there is nothing about DTO in the question. That's because DTO is one of the solutions to this fundamental issue. And the question I started with ('how do I transform entities to DTOs') is merely a follow-up question if I choose to use DTOs. So lets return to the right question. How would I design that kind of system?

The answer is very simple: I would use command and query separation. It becomes obvious when you form the question that way. Points 2 and 3 are separate requirements in the specification. Why should I mix them and try to solve these two problems with one piece of code? And here comes the fourth point: doesn't the CQS solution cause code duplication? It requires me to have two classes for each domain object. That is right, but that is also the requirement of DTO pattern. CQS requires also a duplicate object/relational mapping. That's the place where DTOs seem to be better. But really, are they?

The answer is: no, they are not. It came to me when I was thinking about AutoMapper. Isn't it called object/object mapping tool? So it requires a mapping configuration, right? Same as CQS, only in a different form. But what about simple DTO transformation or assembler pattern? They are also an implementation of mapping logic, only done in imperative style.

To sum up: both CSQ and DTO need a set of additional classes and both need a mapping specification per class. The cost is the same, but the benefits of having CQS is far greater than DTO. In fact there is no benefit of having DTOs at all. They simply solve the problem. CQS, on the other hand, gives you also scalability and flexibility in building UI - you can't overestimate the value of them to your project.

opublikowano 9 września 2009 19:46 przez simon | 0 komentarzy