SpaceX (CC0 1.0)

Zamýšlel jsem se nad tím, jak by programátor mohl naložit s druhou dekádou své kariéry. Jednou z možných pozic je i softwarový architekt. S laskavým svolením Roberta C. Martina, mimo jiné autora knihy Clean Code, přináším překlad jeho rozhovoru mistra s učněm o softwarové architektuře.

4. ledna 2016

Chci se stát softwarovým architektem.

To je pěkný cíl pro mladého softwarová vývojáře

Chci vést tým a dělat všechna důležitá rozhodnutí o databázích, frameworcích, webových serverech a podobných věcech.

Uf. Takže se vůbec nechceš stát softwarovým architektem.

Ovšem že chci. Chci být někdo, kdo dělá všechna důležitá rozhodnutí.

To je v pořádku, ale nevyjmenoval jsi důležitá rozhodnutí. Zmínil jsi ta irelevantní.

Co tím myslíš? Databáze není důležité rozhodnutí? Víš kolik za ně utrácíme?

Pravděpodobně příliš. A ne, databáze není jedno z nejdůležitějších rozhodnutí.

Jak to můžeš říct? Databáze je srdce systému. Je to místo, kde jsou všechna data organizovaná, setříděná, zaindexovaná a přístupná. Bez nich bych žádného systému nebylo!

Databáze je pouze IO zařízení. A zrovna poskytuje užitečné nástroje pro třídění, dotazování a reportování, ale to je pro architekturu systému jen pomocné.

Pomocné? To je šílené.

Ano, pomocné. Byznys pravidla tvého systému můžou z těchto nástrojů nějak profitovat, ale tyto nástroje pro ně nejsou podstatné. Pokud bys musel, mohl bys je nahradit jinými nástroji, ale tvá byznys pravidla by zůstala stále stejná.

No jo, ale musel bych je naprogramovat znovu, protože používají nástroje původní databáze.

Tak to je tvůj problém.

Co tím myslíš?

Tvůj problém je to, že věříš, že byznys pravidla záleží na nástrojích databáze. Nezáleží. Nebo by alespoň neměla, pokud jsi vytvořil dobrou architekturu.

To je šílený rozhovor. Jak můžu vytvořit byznys pravidla tak, že nepoužiju nástroje, které musí byznys pravidla použít.

Neřekl jsem, že nepoužijí nástroje databáze. Řekl jsem, že by na nich neměla záviset. Byznys pravidla by neměla vědět jakou konkrétní databázi používají.

Jak získáš byznys pravidla, aniž bys o nich věděl?

Obrátil jsi závislost. Databáze musí záviset na byznys pravidlech. Ujisti se, že byznys pravidla nezávisí na databázi.

Blábolíš.

Právě naopak, mluvím jazykem softwarové architektury. To je princip Dependency Inversion. Třídy nižší úrovně abstrakce by měly záviset na třídách vyšší úrovně abstrakce.

Nesmysl! Vyšší úrovně abstrakce (předpokládám, že tím myslíš byznys pravidla) volají nižší úrovně abstrakce (předpokládám, že tím myslíš databázi). Takže vyšší úrovně abstrakce závisí na nižších úrovních abstrakce stejným způsobem, jako volající závisí na volaném. To přeci ví každý!

V běhu je to pravda. Ale při kompilaci chceme závislosti obrátit. Zdrojový kód vyšší úrovně abstrakce by neměl zmiňovat zdrojový kód nižší úrovně abstrakce.

Ale no tak! Nemůžeš volat něco, aniž bysto to zmínil.

Samozřejmě že ne. O tom celém je objektová orientace.

Objektová orientace je o vytváření modelů skutečného světa, je o kombinování dat a funkcí do soudržných objektů. Je o organizování kódu do intuitivní struktury.

To ti řekli?

To ví každý, je to zřejmá pravda.

Bezpochyby. Bezpochyby. A přesto užitím principů objektové orientace můžeš skutečně zavolat něco, aniž bys to zmínil.

Dobrá. Jak?

Víš, že objekty navržené objektově orientované si navzájem posílají zprávy?

Ano. Jistě.

A víš, že odesílatel zprávy nezná typ příjemce?

Záleží na jazyku. V Javě zná odesílatel alespoň základní typ příjemce. V Ruby zná odesílatel alespoň to, že příjemce zvládne odeslanou zprávu zpracovat.

Pravda. Ale ani v jednou případě nezná odesílatel přesný typ příjemce.

Jo. Jistě.

Takže odesílatel může zavolat funkci příjemce, aniž by znal přesný typ příjemce.

Jo. Správně. Chápu. Ale odesílatel bude stále záviset na příjemci.

V běhu ano. Ale ne při kompilaci. Zdrojoví kód odesílatele nezmiňuje a ani nezávisí na kódu příjemce. Zdrojový kód příjemce ve skutečnosti závisí na zdrojovém kódu odesílatele.

Ne! Odesílatel stále závisí na třídě, které zasílá zprávy.

Trocha zdrojového kódu to snad ozřejmí. Napíšu to v Javě. Nejprve balíček sender:
package sender;

public class Sender {
  private Receiver receiver;

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

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

  public interface Receiver {
    void receiveThis();
  }
}
Pak balíček receiver.
package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {
  public void receiveThis() {
    //do something interesting.
  }
}
Povšimni si, že balíček receiver závisí na balíčku sender. Rovněž si všimni, že SpecificReceiver závisí na Sender. Taky si všimni, že cokoliv v balíčku sender neví vůbec nic o balíčku receiver.

Jo, ale podváděl jsi. Dal jsi rozhraní příjemce do třídy odesílatele.

Zelenáči, začínáš tomu rozumět.

Rozumět čemu?

Principům architektury samozřejmě. Odesílatel vlastní rozhraní, které musí příjemci implementovat.

No pokud to znamená, že musím používat vnořené třídy, tak…

Vnořené třídy jsou jen jedním způsobem, jak toho dosáhnout. Jsou i jiné.

Dobrá, počkej. Co to má dělat s databázemi? Tím jsme naši konverzaci začali.

Podívejme se na další kód. Nejprve jednoduché byznys pravidlo.
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();
  }
}

Tohle byznys pravidlo toho moc nedělá.

To je jen příklad. Měl bys mnoho podobných tříd implementujících hodně různých byznys pravidel.

Ok, a co je Gateway tentononc?

Poskytuje metody na získání dat použitých v byznys pravidle. Je implementován následovně:
package businessRules;

import entities.Something;

public interface BusinessRuleGateway {
  Something getSomething(String id);
  void startTransaction();
  void saveSomething(Something thing);
  void endTransaction();
}
Všimni si, že je v balíčku businessRules.

Jo ok. A co za třídu je Something?

Ta reprezentuje jednoduchý byznys objekt. Vložil jsem ji do balíčku jménem entities.
package entities;

public class Something {
  public void makeChanges() {
    //...
  }
}
A konečně implementace BusinessRuleGateway. To je ta třída, která ví o aktuální databázi:
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
  }
}
Znovu si všimni, že byznys pravidla v běhu volají databázi, ale při kompilaci je to balíček database, který zmiňuje a závisí na balíčku businessRules.

Ok, ok, myslím, že chápu. Prostě používáš polymorfismus, abys před byznys pravidly skryl implementaci databáze.

Ne, vůbec ne. Nepokoušíme se byznys pravidlům poskytnout všechny nástroje databáze. Raději mějme rozhraní byznys pravidel jen pro to, co potřebují. Implementace těchto rozhraní může volat vhodný nástroj.

Jo, ale když byznys pravidla potřebují všechny nástroje, pak je prostě musíš všechny dát do rozhraní gateway

Ach, vidím, že tomu stále ještě nerozumíš.

Nerozumím čemu? Mně se to zdá naprosto jasné.

Každé byznys pravidlo definuje rozhraní jen pro přístup k tomu, co potřebuje.

Počkej. Cože?

Nazývá se to princip oddělení rozhraní (Interface Segregation Principle – ISP). Každá třída byznys pravidla použije z možností databáze pouze něco. A tak každé byznys pravidlo poskytne rozhraní, které k dané možnosti poskytuje přístup.

Ale to znamená, že budeš mít spoustu rozhraní a spoustu malých tříd (implementujících tato rozhraní), které volají další databázové třídy.

Dobře. Zdá se, že tomu začínáš rozumět.

Ale to je nepořádek a ztráta času. Proč bych to dělal?

Dělal bys to proto, abys udržel pořádek a ušetřil čas.

Ale no tak. Je to jen spousta kódu.

Právě naopak. Jsou to důležitá architektonická rozhodnutí, která ti dovolí odložit irelevantní rozhodnutí.

Co tím myslíš?

Pamatuješ, jak jsi říkal, že bys chtěl být softwarový architekt? Že chceš dělat opravdu důležitá rozhodnutí?

Ano, to chci.

Mezi rozhodnutími, která jsi chtěl dělat, byly databáze, webové servery a frameworky.

Jo a ty jsi říkal, že to nejsou důležitá rozhodnutí, že jsou irelevantní.

Správně, to jsou. Důležitá rozhodnutí, která dělá softwarový architekt, jsou ta, která ti dovolí nedělat rozhodnutí o databázi, webových serverech a frameworcích.

Ale nejprve tato rozhodnutí musíš udělat.

Ne, nemusíš. Vskutku chceš, aby ti bylo dovoleno udělat tato rozhodnutí mnohem později ve vývojovém cyklu, až když máš víc informací.
Běda architektovi, který předčasně rozhodne o databázi a pak zjistí, že souborový systém by býval stačil.
Běda architektovi, který předčasně rozhodne o webovém server a pak zjistí, že jediné, co tým opravdu potřeboval, bylo jednoduché socketové rozhraní.
Běda týmu, jehož architekt na ně předčasně uvalí framework, je aby zjistil, že framework poskytuje sílu, kterou nepotřebují, ale přidává omezení, se kterými nedokážou žít.
Požehnaný tým, jehož architekt poskytl prostředky, díky kterým můžou tato rozhodnutí odložit na dobu, kdy budou mít dost informací.
Požehnaný tým, jehož architekt je izoloval od pomalých a na zdroje náročných IO rozhraní a od frameworků, takže můžou vytvořit rychlé a odlehčené testovací prostředí.
Požehnaný tým, jehož architekt se stará o to, co má skutečně smysl, a odkládá věci, které ho nemají.

Hloupost. Vůbec tě nechápu.

No snad za deset let nebo tak nějak... Pokud tou dobou nebudeš manažer.

Související