Techniki skutecznej komunikacji w projekcie i kodzie – materiały

Witam,

poniżej przedstawiam materiały dla osób chętnych, by dowiedzieć się więcej:

Agile

1. Książka: Jeff Sutherland „Scrum: The Art of Doing Twice the Work in Half the Time”

2. YouTube: „Spotify Engineering Culture”
część 1:  https://www.youtube.com/watch?v=Mpsn3WaI_4k

część 2:  https://www.youtube.com/watch?v=X3rGdmoTjDc

Test-Driven Development

1. Książka: Steve Freeman & Nat Pryce „Growing Object-Oriented Software Guided by Tests”

2. Książka: Kent Beck „Test Driven Development: By Example”

3. Książka: Gojko Adzic „Specification by Example: How Successful Teams Deliver the Right Software”

4. Cykl screencastów: http://www.jamesshore.com/Blog/Lets-Play

5. Narzędzie: Spock https://code.google.com/p/spock/

6. Przykłady do Spock’a: https://github.com/spockframework/spock-example

Domain-Driven Design

1. Książka: Eric Evans „Domain-driven Design: Tackling Complexity in the Heart of Software”

2. Książka: Vaughn Vernon „Implementing Domain-Driven Design”

3. Cykl artykułów: http://www.bottega.com.pl/artykuly-i-prezentacje

Geecon 2015 – podsumowanie

Dzięki uprzejmości firmy e-point, w której obecnie pracuję, miałem okazje uczestniczyć w tegorocznej edycji konferencji Geecon. Poniżej relacja z kilku ciekawszych prezentacji.

Jonas Boner „Life Beyond the Illusion of Present”

Prezenter jest bardzo ciekawą postacią. To twórca projektu Akka i współtwórca manifestu Reactive, Java Champion i współzałożyciel firmy Typesafe. W tym wykładzie przedstawił wpływ doświadczeń z przeszłości na sposób projektowania przyszłych systemów. Stwierdził, że w systemach rozproszonych trzeba zmienić podejście do kwestii czasu. Czas powinien być głównym wyznacznikiem systemu. Co to oznacza?

  • Dane nie powinny być ani aktualizowane ani usuwane, ponieważ historia wpływa na teraźniejszość. Miejsce dyskowe jest tanie.
    cruf
  • Należy być gotowym na porażkę. Taki jest świat. Należy unikać Apology-Oriented Programiming , za to przygotować pożądnie aplikację na radzenie sobie z awarią.
    apologise dd
  • Stan obiektów nie powinien być wspołdzielony. Powinien być dobrze chroniony przed dostępem z zewnątrz. Preferowane są obiektu „immutable” i funkcje bez efektów ubocznych.
    media-20150513

 

Manuel Bernhardt „3 things you must know to think reactive”

Autor książki „Building reactive web-applications with Play” w prezentacji omówił trzy kluczowe koncepty programowania funkcyjnego, które są również używane do budowy aplikacji type „reactive”:

  • immutable objects,
  • higher-order functions,
  • collection operations.

Rozpoczął jednak od wskazania czym są aplikacje „reactive”.

reactive
Ten typ tworzenia systemu zakłada, że system będzie:

  • elsastyczny, lepiej skalowalny,
  • responsywany, aplikacja zawsze jest w stanie przetworzyć akcję użytkownika,
  • odporny na awarie, aplikacja jest w stanie sama sie naprawić,
  • komunikacja pomiedzy komponentami odbywać się będzie za pomocą komunikatów.

Ułatwi to tworzenie systemów rozproszonych i usunie z drogi problemy współbieżnego dostępu do danych:

concur

Poza tym:

imutreac

Gleb Smirnov „Java Concurrency Under The Hood”

gleb

Prezenter przypomniał model pamięci w Javie (JMM) i w jaki sposób dane są współdzielone przez wiele wątków. Na przykładzie działania operatora volotaile i używając narzędzia jcstress przedstawił sposób, w jaki kompilator i JVM optymalizuje dostęp do pamięci. Nie zabrakło również przyjrzenia się kodowi źródłowemu OpenJDK w wersji 8 w celu poznania wewnętrznej implementacji operacji przypisania wartości do zmiennej w pamieci. Z tego wykładu płyną dwa przesłania:

  • trzeba dobrze poznać model pamięci w javie, by móc projektować solidne systemy współbieżne i rozproszone. Na przykład, jeśli dwa wątki pracują na tych samych polach instancji klasy i zmiany zrobione przez jeden wątek mają być odrazu widoczne przez drugi wątek, wtedy należy używać operatora volatile w odniesieniu do pol klasy,
  • nie należy bać się do przeglądania źródeł dystrybucji JDK, by sprawdzić jak działa wewnętrznie. To nie takie straszne!

Wpis na blogu autora na ten temat: http://gvsmirnov.ru/blog/tech/2014/02/10/jmm-under-the-hood.html

Tom Bujok „10 NoSQL databases you have to know”

Prezenter w Polsce znany jako współzałożyciel serwisu z ogłoszeniami o pracę nofluffjobs.com.

media-20150514

Natomiast w tej prezentacji przedstawił cztery bazy NoSQL spośród zaprezentowanych na slajdzie:

  • Redis – baza działająca w pamieci, dzięki temu jest dużo szybsza niż inne rozwiązania, gdyz nie zapisuje na dysk do transaction logu. Jest to najpopularniejsza baza NoSQL typu klucz-wartość. Baza wspiera replikację w trybie master-slave ale klastrowanie dopiero powstaje.
  • MongoDB – duża skalowalność, wydajność i wolność w doborze struktury przechowywanych danych. Dane najczęściej składowane jako dokumenty JSON, co idealnie nadaje sie do trzymania stanu obiektów.  Dużą wadą jest brak pełnego wsparcia dla transakcji, przez co wykorzystawana jest do przetrzymywania niekrytycznych danych aplikacji. Używany na przykład w eBay do trzymania metadanych, informacji o konfiguracji klastra.
  • HBase – nierelacyjna, rozproszona baza danych zbudowana na podstawie konceptu Google’s BigTable jak część platformy s Apache Hadoop i wykorzystująca jej system plików HDFS. Służy do składowania dużej ilości danych. Jeśli chodzi o trwałość danych, to ta baza jest kompromisem pomiędzy bazami w pamięci a trzymanymi w całości w pamieci dyskowej. Wykorzystana w Facebooku do przechowywania wiadomości użytkowników.
  • Cassandra – rozproszona, zdecentralizowana baza danych, zaprojektowana do przechowywania dużej ilości danych, zapewniająca duża wydajność bez SPOFa. Bardzo dobrze sie replikuje i skaluje (liniowa przy dodawaniu nowego węzła), można wybierać poziom spójności danych vs szybkość. Używana przez NetFlix do przechowywania praktycznie wszystkich danych – 90 klastrów, 2700 wezlów, 4 data centers.

Test-Driven Development – tworzenie testu

Wszystkie etapy w procesie wytwarzania oprogramowania za pomocą TDD można zamknąć w kilkunastu krokach:

  • na początku należy wybrać funkcję systemu, którą ma zostać stworzona,

  • napisać test do zweryfikowania wybranej funkcji,

  • uruchomić testy systemu, żeby zobaczyć, iż zakończyły się niepowodzeniem,

  • w wybranej funkcji trzeba zidentyfikować operacje składowe,

  • po wybraniu jednej konkretnej metody należy napisać dla niej test,

  • napisać najprostszą możliwą implementację dla tej metody,
    która powinna ograniczać się tylko do zwrócenia oczekiwanej wartości,

  • kolejny raz należy uruchomić testy systemu,

  • czas na rozbudowanie logiki testu na bardziej szczegółowy,

  • dalej należy zmienić implementację kodu źródłowego na taką, która będzie odzwierciedlać realizowane przez nią procesy biznesowe a zarazem przejdzie wcześniej napisany test,

  • trzy poprzednie punkty należy powtarzać, do momentu aż uzyska się prosty i przejrzysty kod, który przejdzie test,

  • w tym momencie czas na kolejny test i kompilację tworzonego systemu,

  • dla każdej wyodrębnionej metody z wybranej funkcji systemu należy powtórzyć kroki od czwartego do jedenastego.

  • teraz wszystkie te kroki należy powtórzyć dla każdej funkcji systemu.

Jak widać jest to szczegółowe rozwinięcie jednej z głównych zasad metodyki wytwarzania oprogramowania sterowanego testami, czyli „Red, Green, Refactor”.

Test-Driven Development – rodzaje mocków

Żeby lepiej pisać testy należy poznać jeszcze rodzaje obiektów typu Test Double, które są niezbędne przy pisaniu nawet najprostszych testów. Obiekty Test Double mogą zostać podzielone na:

  • stubs, czyli możliwie najprostsze implementacje konkretnego interfejsu,
    które często sprowadzają się tylko do wyrażenia zwracającego domyślną wartość dla operacji,

  • spies, udostępniają funkcjonalność Stub’ów oraz dodają dodatkowo możliwość pamiętania sposobu, w jaki zostały wywołane,

  • fakes, są bardziej rozbudowane od Stub’ów w tym sensie, że posiadają alternatywną implementację, która z powodu swojego zbytniego uproszczenia nie może być użyta w środowisku produkcyjnym,

  • dummies, czyli atrapy parametrów metod i poza tym nigdzie nie używane.

  • mocks, to komponenty, które poza tym, że implementują interfejs obiektu, który zastępują, pozwalają na sprawdzenie sposobu wywołania swoich metod, jak i parametrów przekazanych do ich wnętrza czy kolejności ich wywołania. W zależności od konfiguracji, mogą zwracać na sztywno przypisane im wartości lub zapewniać fałszywą implementację logiki biznesowej. Często są dynamicznie tworzone przez biblioteki zewnętrzne,
    ale mogą być równie dobrze napisane własnoręcznie.

To, który rodzaj atrapy Test Double wybrać, zależy od rodzaju i od celu testu. O ile trzy pierwsze są dość proste i nie budzą większych wątpliwości, należałoby skupić się na ostatnim typie – mock’ach.

Obiekty typu Mock są najbardziej zaawansowanym sposobem na przetestowanie operacji komponentu, od którego zależy testowany system (ang. SUT – System Under Test). Cała praca z tym rodzajem atrapy polega na stworzeniu obiektu, który implementuje interfejs rzeczywistego komponentu a następnie przypisania mu stanu, w którym powinien się znaleźć po jego użyciu w systemie. Tak skonfigurowany komponent należy udostępnić aplikacji w ramach podmiany prawdziwego obiektu. Następnie po zakończeniu testu, potrafi on zweryfikować czy został poprawnie użyty i jeśli nie zakończyć testy błędem.

Warto zatem używać obiektów mock wszędzie tam, gdzie tworzony system komunikuję się z zasobami zewnętrznymi, w sytuacjach gdy chcemy sprawdzić sposób jego  komunikacj. Podstawowymi cechami, które charakteryzują obiekty Mock, są:

  • prostota stworzenia,

  • prostota konfiguracji kontekstu uruchomienia,

  • determinizm.

Dlaczego warto używać takiego mechanizmu? Dlatego, że rzeczywiste obiekty
są niedeterministyczne i ciężko przewidzieć ich zachowanie. Poza tym dużo problemów nastręcza konfiguracja ich środowiska działania jak i szybkość jego uruchomienia. Dużo czasu zajmują równiez testy elementu, który posiada interfejs graficzny. Rzeczywiste obiekty często nie pozwalają na sprawdzenie swojego stanu, przez co obiekty Mock mimo, że są zwane atrapami prawdziwych obiektów są od nich bardziej rozbudowane udostępniając dodatkową funkcjonalność. Pozwalają na testowanie sytuacji wyjątkowych, które w normalnych warunkach działania programu zdążają się rzadko i są ciężkie do wychwycenia oraz zdiagnozowania. Możecie znaleźć się w sytuacji, która wymusi korzystanie z Mock’ów obiektów, na przykład w przypadku, gdy rzeczywisty obiekt jeszcze nie istnieje.

Używanie Mock’ów pozwala skupić się na testowaniu funkcjonalności systemu
i nie zwracaniu uwagi na sposób jego komunikacji z innymi zdalnymi obiektami
a raczej na sposobie ich użycia w aplikacji. Po fazie testowania, w miejsce atrap wstawiane są prawdziwe obiekty i wtedy system podlega weryfikacji za pomocą testów integracyjnych.

Test-Driven Development – pisanie dobrych testów

Mimo, że nie ma jedneje definicji dobrego testu jako takiego, istnieją natomiast wytyczne i różnego rodzaju heurystyki, dzięki którym można stwierdzić czy pisane przez nas testy są na przyzwoitym poziomie. O ile wraz z narzędziami do tworzenia testów jednostkowych dostarczane są liczne wzorce, to o tyle dla procesu budowania testów akceptacyjnych na podstawie wymagań użytkowników końcowych można zastosować dwie zasady:

  • zasadę atomowości,

  • zasadę izolacji.

W ogólności oznacza to że testy powinny być niewielkich rozmiarów, scentralizowane, atomowe i niezależne od innych. Niezależność testów między sobą nie polega na wykonywaniu ich w losowej kolejności a raczej nie pokrywaniu się ich odpowiedzialności. Pisanie dobrych testów wymaga umiejętności programowania intencyjnego (ang. Programming by Intention). Pozwala to programiście na skupienie się na kodzie pod względem jego funkcjonalności aniżeli sposobie jego realizacji. Główne zalety to poprawa czytelności, a co za tym idzie jego łatwiejsza analiza i użycie. W takim kodzie można zrozumieć intencje autora kodu, jak i łatwo odnaleźć główne przebiegi procesów.

Lasse Koskela w książce „Test Driven: Practical TDD and Acceptance TDD for Java Developers” podaje kilka wskazówek, które wypracował podczas swojej długoletniej pracy z tą metodyką. Pierwsza z nich mówi o przestrzeganiu kolejności pracy nad projektem w tej metodyce. Szczególny nacisk kładzie na ostatnią fazę spośród wzorca „test – code – refactor”. Jako główny problem przy tworzeniu systemów wskazuje brak wprowadzania poprawek w kodzie źródłowym klasy komponentu i testu.

Druga rada mówi, by jak najszybciej i jak najmniejszym kosztem doprowadzić do przejścia testów. Może to zabrzmieć dość niespodziewanie, ale etap kodowania
to etap tworzenia jak najprostszej i jak najbardziej trywialnej implementacji, która dopiero w fazie refactoringu nabiera ostatecznego kształtu

Przy każdym wystąpieniu błędu należy zwolnić tępo pracy i poświecić go więcej na implementację. Wiele błędów pochodzi z niedbalstwa i pośpiechu w realizacji zadania.

Za dobrą praktykę przyjęło się unikanie redundancji kodu w momencie projektowania środowiska uruchomieniowego dla testów. Poprzez środowisko uruchomieniowe rozumiane jest wszystko to, czego potrzebujemy przy przeprowadzeniu procesu testowania, skonfigurowane w odpowiedni sposób. Począwszy od wirtualnej maszyny Javy, a na inicjalizacji obiektu testowanego skończywszy. Tak skonfigurowany kontekst (ang. fixture) musi być wspólny dla uruchamiania wszystkich testów i nigdy nie powinien być tworzony oddzielnie dla każdej jednostki. Dzięki stworzeniu takich warunków uzyskujemy prostotę i łatwość w tworzeniu testów, gdyż cały kontekst uruchomienia posiadamy skonfigurowany już na starcie i możemy skupić się tylko i wyłącznie na pisaniu testu.

Rodzaje testowania

Jednak największą bolączką testerów oprogramowania są przypadki, gdzie obiekt
do walidacji jest mocno powiązany z zasobami zewnętrznymi, takimi jak dostęp do dysku czy połączenia z bazą danych. Na szczęście powstało rozwiązanie, znane jako Test Double. Są to obiekty tworzone jako odpowiedniki zasobów zewnętrznych, które realizują ich odpowiedzialność w sposób szybszy i to pod względem czasu wykonania kodu, jak i czasu potrzebnego na napisanie i zarządzanie testem. Cykl pracy z Test Double zaczyna się od jego stworzenia, skonfigurowania jego zachowania, a następnie użycia w kodzie testu, by sprawdzić poprawność implementacji produkcyjnej.

Zanim zostaną przedstawione rodzaje obiektów Test Double, które są bardzo istotne przy testowaniu, dokonam rozdzielenia pomiędzy rodzajami testów.

Pierwszymi z nich są testy stanu komponentu (ang. State-Based Testing), które porównują wartości przechowywane w polach komponentu z naszymi oczkiwaniami zawartymi w wywołaniach metod mocków.

Z drugiej strony, możemy chcieć zweryfikować sposób realizowania zależności pomiędzy testowanymi obiektami, bardziej niż badać jego stan końcowy. Ta metoda skupia się na walidacji czy testowany obiekt wywołał odpowiednie metody z odpowiednimi parametrami. Nosi ona nazwę testowania bazującego na interakcjach (ang. Interaction-Based Testing) i jest używana do walidacji bardziej złożonych obiektów z wieloma zależnościami, gdzie proste sprawdzanie stanu nie wystarcza.

Test-Driven Development – pierwsze spojrzenie

W języku angielskim ta metodyka znana jest pod kilkoma nazwami. Terminy takie
jak Test-Driven Development, Test-Driven Design czy Test-First Programming wszystkie odnoszą się do sposobu wytwarzania oprogramowania sterowanego testami .

TDD jest rodzajem metodyki zwinnej opartej o proces iteracyjny, w którym w każdym przebiegu tworzone są najpierw testy funkcjonalne, czyli takie w których sprawdzana jest nowa funkcjonalność a dopiero potem kod. Po zakończeniu każdej iteracji powstaje działający w stu procentach kod. W TDD wszystko odbywa się zgodnie
z zasadą „Red, Green, Refactor”, czyli:

  • Red – należy zacząć od stworzenia pierwszych testów na podstawie przypadków użycia,

  • Green – należy napisać najprostszą implementację, która pozwoli na pozytywne przejście tych testów, ale tylko w wypadku gdy poprzednia jej nie spełniała,

  • Refactor – w tym etapie należy ulepszyć implementację, usunąć redundantny kod i poprawić sama architekture systemu przy jednoczesnym zwróceniu uwagi na przejście testów.

Wytwarzanie oprogramowania za pomocą częstych i niedużych pętli inkrementacyjnych pozwoli na stworzenie dobrze przetestowanej implementacji a zarazem bardzo dobrej dokumentacji. Kod takiej aplikacji będzie przejrzyście realizował narzuconą wcześniej funkcjonalność. Poza tym do zysków należy też dopisać czas, gdyż testy akceptacyjne zajmują go więcej aniżeli pisanie  testów jednostkowych.

W metodyce TDD odpowiedzialność za tworzenie testów spada na projektanta
lub programistę. W związku z tym należy pamiętać o fakcie, o którym można przeczytać na stronach firmy Google związanych z testowaniem aplikacji: „If it ain’t broke, you’re not trying hard enough”. Oznacza to tyle, że trzeba zachować szczególną uwagę przy pisaniu testów, gdyż źle napisane lub zbyt skomplikowane testy nie spełnia swojej roli. Niekompletne testy powodują powstanie luk w systemie w postaci błędów w działaniu oprogramowania.

Do rodzaji testów można zaliczyć:

  • testy jednostkowe (ang. Unit Tests), są najprostszą formą sprawdzenia
    czy implementacja jest zgodna z semantyką domeny dla której zostały napisane,

  • testy developerskie (ang. Programmer Tests), czyli takie, w których programista sprawdza swój kod pod kątem realizacji wyznaczonej funkcjonalności. Testy developerskie powinny byc specyficznym rodzajem testów jednostkowych,

  • testy integracyjne (ang. Integration Tests), służą do sprawdzenia komunikacji tworzonego systemu z zasobami zewnętrznymi, takimi jak: inne systemy informatyczne, bazy danych, zasoby dyskowe i tym podobne.
    Architektura wytwarzania oprogramowania sterowanego testami nie obejmuje kontroli zasobów zewnętrznych a jedynie sam tworzony system,

  • Testy użytkownika (ang. User Acceptance Tests), pisane przez użytkownika końcowego aplikacji, sprawdzające zgodność systemu z wymaganiami klienta.

Przy tworzeniu systemów informatycznych zgodnych z metodyka TDD należy również pamiętać o tym, że żadna implementacja nie trafia do środowiska produkcyjnego jeśli nie posiada związanego z nią testu, który ja zweryfikował. To gwarantuje, że klient otrzyma dobrze przetestowany system bez złożoności przypdakowej. Testy są pisane jeszcze przed właściwym kodowaniem co zmniejsza nakład pracy przy tworzeniu implementacji, gdyż programista jest zdeterminowany
przez testy do stworzenie tylko niezbędnego kodu. Co zaskajuące, Państwowy Komitet Badań Naukowych w Kanadzie (w skrócie NRC – ang. National Research Council of Canada) w badaniach przeprowadzonych jeszcze w 2005 roku stwierdził, że wraz ze wzrostem liczby testów pisanych przez programistów rośnie poziom ich produktywności.

Dlaczego powstało Test-Driven Development?

Jak większość obecnych metodyk, tak i Test-Driven Development powstało jako dopełnienie wcześniejszych, w tym wypadku Test-First Programing istniejącego już od początku 1999 roku. Podobnie jak wszystkie przed nią musiała się zmierzyć z wyzwaniami i oczekiwaniami stawianymi przez przyszłych projektantów i programistów.

Skoro metodyki skupiające się na testach systemów istniały już wcześniej,
to rodzi się pytanie: „Dlaczego tyle systemów było tak słabo przetestowanych?”. Na tak postawione pytanie nie ma jednoznacznej odpowiedzi. Istnieje kilka.

Po pierwsze należy zauważyć, że nawet używanie najlepszych dostępnych narzędzi nie gwarantuje sukcesu. W tym przypadku było podobnie. Niewyczerpujące, źle napisane testy lub w ogóle ich brak, powodował, że kod obfitował w różnego rodzaju pułapki, które powodowały konieczność wprowadzania kosztownych poprawek, bądź wycofanie z użycia.

Kolejnym błędem było pisanie testów po zakończeniu prac na częścią implementacyjną lub w momencie, gdy osoba za nie odpowiedzialna zajmowała się już czymś innym. W takich sytuacjach jest już za późno na napisanie testów, a te które powstaną nie obejmują całej dziedziny problemowej i są kompletnie nieprzydatne. Dlatego TDD wymusza na deweloperach pisanie testów przed stworzeniem jakiejkolwiek implementacji. Gwarantuje to, że twórca w pełni rozumie zagadnienie nad którym pracuje.

Metodyka wymaga, by testy jednostkowe pisane były przez osobę, która będzie odpowiadać za implementację. Związane jest to z faktem, że pisanie testówpozwala odkrywać istotne aspekty domeny.

Kolejnym możliwym źródłem problemów, może być sposób, w jaki wykonywane są testy. Jeśli nie są zautomatyzowane, to są nikłe szanse, aby były wykonywane regularnie i zawsze w identyczny sposób. Nie wykonają zadania do którego zostały stworzone.

Ostatnia ważna rzecza jest tworzenie testów w taki sposób, żeby chroniły system przed wprowadzaniem błędów regresyjnych. Przy tym trzeba pamiętać, że wraz ze zmianami implementacji powinny zmieniać się testy. Architektura testów jest tak samo ważna jak architektura implementacji.

Przy użyciu zaleceń i procedur postępowania dostarczonych przez metodykę Test-Driven Development można uniknąć wszystkich powyższych problemów.

Domain-Driven Design Context Map

Jako, że w dużych systemach istnieje wiele modeli umieszczonych w osobnych „Bounded Context”  to musza istnieć sposoby komunikacji pomiędzy konteksami. DDD dostarcza kilka sposobów realizacji „Context Map”.

Shared kernel

Czasami koszt translacji pomiedzy obiektami z różnych domen może być zbyt duży.  Być może narzut na nią jest wiekszy niz korzyść z posiadania oddzielnych kontekstów. Wtedy warto stworzyć wspólny wycinek domeny, który projekty godzą się współdzielić. Oczywiscie wiaze sie to z wspoldzieleniem kodu i wycinka modelu bazy danych. Jest to artefkat, którego nie można zmieniać bez konsultacji z innymi beneficjentami tego rozwiązania. Dodatkowo każdy zespół używający wspólnego modelu powininen posiadać testy integracyjne, które pilnują kontraktu pomiedzy modelem w projekcie a wspólnym modelem.

Celem posiadania wspólnego modelu jest zmniejszenie duplikacji konceptów pomiędzy modelami ale jednocześnie zapewnienie istnienia osobnych kontekstów.

Customer-Supplier

Jest to relacja pomiedzy modelami, gdzie jeden jest klientem drugiego. Sztampowy przykład to dowolna domena biznesowa, która musi posiadać funkcjonlaność raportów. Pierwszym oczywistym podziałem takiej domeny jest podział domeny na dwa modele: właściwy i raportowania. Model właściwy nie powinien wiedziec nic o raportowaniu,  miejsce utrwalania danych nie powinno być wspolne. Model właściwy powinien zostac utrwalnony w strukturze, która najlepiej odpowiada jego potrzebom, na przykład w znormalizowanej struktrurze bazy danych. Z drugiej strony, dla modelu raportowania najbardziej odpowiednia bedzie zdenormalizowanej struktura.

Jak polaczyc te dwa wymagania infrastrukturalne? Jako, ze sa to oddzielne modele to powinny posiadac osobne struktury utrwalania danych. Relacja jaka łączy te modele to klient-dostawca, więc model dostawcy moze publikować zdarzenia podczas utrwalania dancyh, ktore model klienta moze przechwytywac i utrwalac wedle swoich wymagan. Tak wiec powstana dwa schematy bazy danych: jeden dla modellu właściwego i drugi do raportowania. Dzieki temu beda mogly ewaluowac niezaleznie.

Oczywiscie ta relacja ujawnia sie nie tylko w stosunku do utrwalania danych, ale również wykonywania innych operacji biznesowych. Wymieniane interfejsy pomiedzy domenami powinny byc jasno zdefiniowane i rowniez pilnowane przez testy.

Anticorruption Layer

Często zdarza się, że nowo powstające systemy muszą się integrować z systemami „legacy”. Integracja może odbywać się przez sieć czy za pomocą wspolnej bazy danych i jest pozbawiona zupełnie semantyki. Model zewnętrzny widoczny jest jako zbiór danych i relacji, bez okreslenia w jaki sposob rozwiazauje problemy domenowe i jakie to sa problemy.

Nie możemy zignorować komunikacji z modelem zewnętrznym, ale musimy pamiętać żeby odizolować nasz model od niego. Trzeba wiec zbudować pewną warstwę translacji, znaną w DDD jako „Anticorruption Layer”, która wyizoluje nowy model od starego. Elementy tej warstwy komuniikują się ze starym systemem używając jego jezyka. Jest to warstwa tłumacząca pomiędzy dwoma oddzielnymi domenami i jezykami. Najczęsciej  bedzie to serwis zaimplementowana jako Fasada, który prawdopodobnie będzie używał również Adapterów.

Separate Ways

Jest koncept, w którym system budowany jest z kilku mniejszych aplikacji, które mogą nie mieć ze sobą nic wspólnego z punktu widzenia architekta systemu. Z punktu widzenia użytkownika końcowego jest to jeden homogeniczny system, ale z poziomu architektury są to osobne modele zaimplementowane oddzielne, byc może jako mikroserwisy. Przy modelowaniu domeny problemowej powinnismy wyłapywać koncepty, które są nie zależne w syemie i mogą zostać zamodelowane jako osobne „Bounded Context”. Daje nam to możliwość dowolnego decydowania o technologiach użytych do implementacji. Aplikacja moze wspoldzielic bardzo cienka warstwe prezentacji z buttonami i linkami ukrywającymi przed uzytkownikiem heterogonicznosc architektury. Warto dobrze się zastanowić przed podjęciem decyzji o przejsciu na ta koncepcje, by na koniec nie skonczyc na integracji całego aplikacji.
Open Host ServiceKiedy nasz system musi być integrowany z wieloma innymi systemami, wprowadzanie warstwy translacji pomiedzy naszym modelem a kazdym innym systemem moze powodować ogromne wysycenie zasobów zespołu deweloperskiego. Zespół nie będzie miał możliwości pracy nad własnym modelem. Dlatego twórca DDD poleca stworzenie pewnego rodzaju protokołu do integracji z naszym systemem i aktualizowaniu go za kazdym razem, gdy dodalismy nowa operacje do modelu.

Domain-Driven Design Bounded Context

Każdy model powstający na podstawie rozmów z ekspertami domeny jest poprawny, ale tylko w obrebie jakiegoś kontekstu. Gdy tworzymy prostą aplikację, kontekst jest jeden i czesto niejawny. W bardziej skomplikowanych systemach, mogą istnieć pojęcia, które dla różnych ekspertów domenowych znaczą co innego. Być może tens sam koncept nazywany jest inaczej. Dla przykładu, dla pracownika odpowiedzialnego za wystawianie dokumentów, faktura znaczy co innego niż dla księgowego liczącego przychody. Jak zamodelować taki obiekt w systemie. Jak stworzyć wspólny język, „Ubiquitous Language”?

Należy w modelu  wprowadzić ograniczone konteksty (ang. Bounded Contexts). Są Oznaczają części aplikacji, z których każda posiada dokładnie zaznaczone granice i swój model. Każda z nich jest odpowiedzialna za inny aspekt biznesu i dzięki takiemu podziałowi łatwiej stwierdzić, gdzie powinna się znajdować poszczególna logika biznesowa. Bounded Context nie jest równoznaczny z modułem. Moduły używane sa do organizowania elementów modelu, natomiast kontekst jest obszarem o ściśle wyznaczonych granicach, w którym rozwijany jest model.

Poza tym może się zdarzyć tak, że w różnych kontekstach będą używane te same terminy, ale ze względu na miejsce, w którym się znajdują, będą miały różne znaczenie. Dzieje się tak dlatego, że każdy pojedynczy kontekst może posiadać swój język niezależny od pozostałych. Taki podział niesie ze sobą następujące zalety:

  • Modułowość (ang. Modularity)

  • Rozdzielenie pojęć (ang. Separation of Concerns)

  • Luźne powiązania pomiędzy modułami (ang. Loose Coupling)

  • Niezależne rozwijanie kontekstów

Komunikacja i powiązania pomiędzy kontekstami opisane są w dokumencie zwanym mapą kontekstu (ang. ContextMap). Jasno zdefiniowane granice kontekstów mają służyć zlikwidowaniu współdzielenia obiektów i operacji pomiędzy kontekstami. W tym podejściu potrzebna będzie nowa warstwa translacji, ale zysk z posiadania jasno zdefiniowanych kontekst zdaje się przewyższać konieczność jej stworzenia.

Zeby utrzymać jasne granice pomiędzy kontekstami przez całe zycie systemu trzeba zadbać o stworzenie odpowiednich testow integracyjnych, które bedą pilnować kontraktu w warstwie translacji.  Przydadzą się również testy jednostkowe pilnujące konceptów domeny biznesowej w poszczególnych modelach. I to wszystko w postaci Continous Integration.

Domain-Driven Design Specification

Specyfikacje w DDD służą do sprawdzania obiektów domenowych pod kątem zgodności z pewnymi warunkami biznesowymi.

Specyfikacje są osobnym „building block”. Czujni zadadzą pytanie, czy warunki biznesowe związane z pewnym obiektem biznesowym nie powinny zostać enkapsulowane w jednej z metod tego obiektu. Otóż nie. Bardzo często takie warunki są bardzo skomplikowane i operują na danych innych powiązanych obiektów. Dodanie ich w metodzie obiektu domenowego spowodowałoby rozmycie odpowiedzialności obiektu, a więc złamanie reguły „Single Responsibility Principle”. Dlatego powinny trafić do osobnego obiektu i być będącego specyfikacją innego obiektu domenowego. Klasa tego obiektu powinna zawierać metodę, po wykonaniu której da się jednoznacznie stwierdzić czy testowany obiekt jest poprawny. Gdy mamy wiele takich warunków biznesowych, to powinniśmy mieć wiele obiektów specyfikacji w warstwie domenowej.

Z jednej strony specyfikacje służą do sprawdzenia stanu pewnego obiektu domeny i jego gotowości do wykonania jakiejś operacji biznesowej. Z drugiej zaś strony, mogą służyć jako predykaty do wybierania obiektów z kolekcji obiektów.

Powinno przyjąć się założenie, że jeden obiekt specyfikacji odpowiada za testowanie jednego aspektu biznesowego a następnie ileś takich specyfikacji łączymy kompozycją w jedno wyrażanie:

Specification auctionReadyToStart = new Specification(
        new AuctionIsInReadyForStartStatus(),
        new AuctionHasBiddingStartPrice(),
        new AuctionOrganiserIsActive());
if (auctionReadyToStart.isSatisfiedBy(auction)) {
//    start auction logic    
}