22 marca 2017

Trochę architektury

Chcę zostać architektem oprogramowania.

To dobry cel dla młodego dewelopera.

Chcę kierować zespołem i podejmować wszystkie ważne decyzje dotyczące baz danych, frameworków, serwerów webowych i całego tego stuffu.

Acha. No cóż, czyli tak naprawdę nie chcesz być architektem oprogramowania.

Poniższy tekst jest luźnym tłumaczeniem wpisu bloga Roberta Cecila "Wujka Boba" Martina ze strony :
Proszę o komentarze, jeżeli ta luźność jest zbyt daleko posunięta.


Oczywiście, że chcę! Chcę być tym, który podejmuje te wszystkie ważne decyzje.

No dobrze, ale Ty nie wymieniłeś ważnych decyzji. Wymieniłeś te nieistotne.

Co masz na myśli? Baza danych nie jest ważną decyzją? Czy wiesz ile pieniędzy wydajemy na nią?

Prawdopodobnie za dużo. I nie, baza danych nie jest jedna z najważniejszych decyzji.

Jak możesz tak mówić? Baza danych to serce systemu! To tam wszystkie dane są poukładane, posortowane, poindeksowane i dostępne. Bez niej nie byłoby systemu!

Baza danych jest jedynie urządzeniem wejścia - wyjścia. Tak się składa, że dostarcza kilka użytecznych narzędzi do sortowania, wybierania i raportowania, ale są one tylko pomocnicze w stosunku do architektury systemu.

Pomocnicze? To szaleństwo.

Tak pomocnicze. Reguły biznesowe systemu mogą używać niektórych z tych narzędzi; ale te narzędzia nie są powiązane z regułami biznesowymi. Jeżeli musisz, to możesz zastąpić te narzędzia innymi narzędziami; ale reguły biznesowe pozostaną takie same.

No, tak, ale wtedy trzeba by przepisać je, bo one wszystkie używają narzędzi z oryginalnej bazy danych.

No cóż, to Twój problem.

Co masz na myśli?

Twój problem polega na tym, że wierzysz, że reguły biznesowe zależą od narzędzi bazy danych. One nie zależą. Albo przynajmniej nie powinny zależeć jeżeli chcesz stworzyć dobrą architekturę.

Co za wariactwo. Jak mam stworzyć reguły biznesowe, które nie używają narzędzi, których muszą używać?

Nie powiedziałem, że nie używają narzędzi bazy danych. Powiedziałem, że nie powinny od nich zależeć. Reguły biznesowe nie powinny wiedzieć jakiej używają konkretnej bazy danych.

Jak zrobić, żeby reguły biznesowe używały narzędzi nie wiedząc o nich?

Odwracasz zależności. Masz bazę danych zależną od reguł biznesowych. Upewniasz się, że reguły biznesowe nie zależą od bazy danych.

Gadasz bzdury.

Wręcz przeciwnie, mówię językiem Architektury Oprogramowania. To jest Zasada Odwracania Zależności. Niskopoziomowe polityki powinny zależeć od wysokopoziomowych polityk.

Więcej bzdur! Polityki wysokiego poziomu (przypuszczam, że masz na myśli reguły biznesowe) wołają polityki niskiego poziomu (zakładam, że chodzi o bazę danych). A więc polityki wysokiego poziomu zależą od polityk niskiego poziomu w ten sam sposób jak nadawcy zależą od odbiorców. Każdy to wie!

To jest prawda w czasie wykonania programu. Ale w czasie kompilacji potrzebujemy odwróconych zależności. Kod źródłowy wysokopoziomowych polityk nie powinien wspominać kodu źródłowego niskopoziomowych polityk.

No, proszę Cię! Nie możesz wołać czegoś bez wspominania o tym.

Oczywiście, że możesz. Wokół tego kręci się całe Zorientowane Obiektowo.

Zorientowane Obiektowo jest o tworzeniu modeli prawdziwego świata, jest o łączeniu danych i funkcji w spójne obiekty. Jest o organizacji kodu w intuicyjną strukturę.

Czy to właśnie Ci powiedzieli?

Każdy to wie. To jest oczywista prawda.

Bez wątpienia. Bez wątpienia. I jeszcze, wykorzystując zasady programowania obiektowego możesz rzeczywiście zawołać coś bez wspominania o tym.

No, dobra? Jak?

Wiesz, że w projekcie zorientowanych obiektowo - obiekty wysyłają do siebie wiadomości?

Tak. Oczywiście.

I wiesz, że nadawca wiadomości nie zna typu odbiorcy?

To zależy od języka. W Javie ten który wysyła zna przynajmniej typ klasy bazowej tego, który odbiera. W Ruby nadawca wie przynajmniej, że odbiorca może odebrać wysłaną wiadomość.

Prawda. Ale w obu przypadkach nadawca nie zna dokładnego typu odbiorcy.

Taaa. OK. Pewnie.

W związku z tym, nadawca może spowodować, że uruchomienie funkcji u odbiorcy bez wspominania dokładnego typu odbiorcy.

Taaa. Prawda. Łapię to. Ale nadawca nadal zależy od odbiorcy.

W czasie wykonania tak. Ale nie w czasie kompilacji. Kod źródłowy odbiorcy nie wspomina, ani nie zależy od kodu źródłowego odbiorcy. W rzeczywistości kod źródłowy odbiorcy zależy od kodu źródłowego nadawcy.

Nieee! Nadawca nadal zależy od klasy, do której wysyła.

Może kod źródłowy rozjaśni to. Napiszę to w Javie. Najpierw pakiet nadawcy:
package sender;

public class Sender {
  private Receiver receiver;

  public Sender(Receiver r) {
    receiver = r;
  }

  public void doSomething() {
    receiver.receiveThis();
  }

  public interface Receiver {
    void receiveThis();
  }
}
Następnie pakiet odbiorcy:
package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {
  public void receiveThis() {
    //do something interesting.
  }
}
Zauważ, że pakiet odbiorcy zależy od pakietu nadawcy. Zauważ też, że SpecificReceiver zależy od Sender. Zauważ też, że nic w pakiecie nadawcy nie wie o niczym w pakiecie odbiorcy.

No tak, ale oszukiwałeś. Włożyłeś interfejs odbiorcy w pakiet nadawcy.

Zaczynasz rozumieć, Pasikoniku.

Rozumieć co?

Zasady architektury oczywiście. Nadawcy zawierają interfejsy, które odbiorcy implementują.

No cóż, jeżeli to oznacza, że użycie zagnieżdżonych klas to ....
Klasy zagnieżdżone to tylko jeden środek do celu. Są inne.

Do dobra, poczekaj. Co to wszystko ma wspólnego z bazami danych. Od tego zaczęła się nasza dyskusja.

Spójrzmy na trochę więcej kodu. Najpierw reguły biznesowe:
package businessRules;

import entities.Something;

public class BusinessRule {
  private BusinessRuleGateway gateway;

  public BusinessRule(BusinessRuleGateway gateway) {
    this.gateway = gateway;
  }

  public void execute(String id) {
    gateway.startTransaction();
    Something thing = gateway.getSomething(id);
    thing.makeChanges();
    gateway.saveSomething(thing);
    gateway.endTransaction();
  }
}


Ta reguła biznesowa za wiele nie robi.

To tylko przykład. Mógłbyś mieć więcej klas takich jak ta implementujących masę innych reguł biznesowych.

OK, a o co chodzi z tym całym Gateway?

Zapewnia dostęp do danych dla metod używanych przez regułę biznesową. Wygląda tak:
package businessRules;

import entities.Something;

public interface BusinessRuleGateway {
  Something getSomething(String id);
  void startTransaction();
  void saveSomething(Something thing);
  void endTransaction();
}
Zauważ, że jest w pakiecie businessRules.

Taaa, OK. A co to za klasa Something?

Ona reprezentuje prosty obiekt biznesowy. Wrzuciłem ją do pakietu entities.
package entities;

public class Something {
  public void makeChanges() {
    //...
  }
}
I na koniec implementacja BusinessRuleGateway. To jest ta klasa, która wie wszystko o rzeczywistej bazie danych.
package database;

import businessRules.BusinessRuleGateway;
import entities.Something;

public class MySqlBusinessRuleGateway implements BusinessRuleGateway {
  public Something getSomething(String id) {
    // use MySql to get a thing.
  }

  public void startTransaction() {
    // start MySql transaction
  }

  public void saveSomething(Something thing) {
    // save thing in MySql
  }

  public void endTransaction() {
    // end MySql transaction
  }
}
I znowu, zauważ, że reguły biznesowe wołają bazę danych w czasie wykonania; ale w czasie kompilacji to pakiet database wspomina i zależy od pakietu businessRules.

No dobra, dobra, myślę, że rozumiem. Ty po prostu używasz polimorfizmu żeby schować implementację bazy danych przed regułami biznesowymi. Ale nadal musisz mieć interfejs, który dostarcza wszystkie narzędzia bazy danych regułom biznesowym.

Nie, w żadnym wypadku. Nie próbujemy dostarczyć wszystkich narzędzi bazodanowych regułom biznesowym. Raczej, mamy tak, że to reguły biznesowe tworzą tylko takie interfejsy, których potrzebują. Implementacja tych interfejsów może wołać odpowiednie narzędzia.

Tak, ale jeżeli reguły biznesowe potrzebują wszystkich tych narzędzi, wtedy musisz wrzucić wszystkie te narzędzia do interfejsu gateway.

Ech. Widzę, że nadal nie rozumiesz.

Nie rozumiem czego? Wydaje mi się to całkiem jasne.

Każda reguła biznesowa definiuje interfejs tylko do takiego obiektu dostępu do danych, jakiego potrzebuje.

Poczekaj. Co?

To nazywa się Zasada Segregacji Interfejsów. Każda klasa reguł biznesowych będzie używała tylko niektórych obiektów bazy danych. A więc, każda reguła biznesowa dostarcza interfejsu, który daje dostęp tylko do tych obiektów.

Ale to oznacza, że będziesz miał mnóstwo interfejsów i mnóstwo klas, które je implementują wołających inne klasy bazy danych.

No dobrze. Widzę, że zaczynasz rozumieć.

Ale to jest burdel i strata czasu! Dlaczego miałbym tak robić?

Mógłbyś to robić, żeby mieć czysty kod i oszczędzić czas.

No weź, to po prostu pisanie mnóstwa kodu sztuka dla sztuki.

Przeciwnie, to są ważne decyzje architektury, które pozwolą Ci odraczać w czasie decyzje bez znaczenia.

Co przez to rozumiesz?

Pamiętasz, na początku, jak mówiłeś, że chcesz być Architektem Oprogramowania? Chciałeś podejmować te wszystkie ważne decyzje?

Tak, tego właśnie chcę.

Pośród tych decyzji, które chciałeś podejmować były bazy danych, serwery webowe i frameworki.

Tak, i ty powiedziałeś, że one nie były ważnymi decyzjami. Powiedziałeś, że były bez znaczenia.

To prawda. Takie są. Ważne decyzje podejmowane przez Architekta Oprogramowania to te które pozwalają Ci NIE podejmować decyzji o bazach danych, serwerze webowym i frameworkach.

Ale musisz podjąć te decyzje na początku!

Nie nie musisz. W rzeczywistości, chcesz, żebyś te decyzje mógł podjąć w późniejszym cyklu rozwoju - wtedy, kiedy będziesz miał więcej informacji.

Biada architektowi, który przedwcześnie decyduje o bazie danych, i potem dochodzi do wniosku, że płaskie pliki będą skuteczniejsze.

Biada architektowi, który przedwcześnie decyduje o serwerze bazy danych; tylko po to żeby przekonać się, że wszystko to, czego potrzebował zespół to otwarcie jednego gniazda sieciowego.

Biada zespołowi ludzi, w którym architekci przedwcześnie narzucają im framework, tylko by przekonać się, że framework dostarcza funkcjonalności, których nie potrzebują i dodaje ograniczeń, z którymi nie mogą żyć.

Błogosławiony zespół, w którym architekci dostarczyli środków, dzięki którym wszystkie te decyzje mogą być odłożone, aż będzie wystarczająco dużo informacji, aby je podjąć.
Błogosławione zespoły, w których architekci odizolowali je od wolnych i zasobożernych urządzeń wejścia/wyjścia i frameworków, że mogą dzięki temu stworzyć szybkie i lekkie środowiska testowe.

Błogosławiony zespół, w którym architekci dbają o to, co się naprawdę liczy i odkładają te rzeczy, które są nieważne.

Bzdury. Nie kupuję tego.

No cóż, być może uznasz to za swoje w ciągu dekady lub więcej …… no chyba, że zostaniesz do tego czasu menadżerem.

Poniższy tekst jest luźnym tłumaczeniem wpisu bloga Roberta Cecila "Wujka Boba" Martina ze strony :
http://blog.cleancoder.com/uncle-bob/2016/01/04/ALittleArchitecture.html
Proszę o komentarze, jeżeli ta luźność jest zbyt daleko posunięta.

4 komentarze:

  1. Kupuję to. Czy widziałeś firme w której to tak działa? W której kod biznesowy nie wie o technikaliach? Czy to może czysta idylla..

    OdpowiedzUsuń
    Odpowiedzi
    1. Cześć Łukasz,

      Pierwsze co przyszło mi do głowy to Spotify:

      "Clients for desktop, mobiles and our embeddable library, libspotify, all share a common code base. "

      https://www.quora.com/What-is-Spotifys-architecture

      Usuń
  2. Pierwsze co mi przyszło do głowy czytając - już to gdzieś czytałem tylko po angielsku.
    Imho końcówka napisana małą wyszarzoną czcionką powinna być na początku wielkimi literami.

    OdpowiedzUsuń
  3. Hej, Anonimowy,
    Rzeczywiście info o autorstwie Wujka Boba nadaje się tak na koniec, jak i na początek.

    OdpowiedzUsuń

Podstawy Programowania Funkcyjnego Epizod 3

Czy wszystkie Zasady Się Zmieniają? Kiedy tylko zaczynamy używać nowego paradygmatu , porównujemy z nim na...