Po ponad 30 latach tworzenia oprogramowania — od wczesnych systemów klient-serwer po nowoczesne architektury chmurowe — nazbierałem sporo blizn. Niektóre od technologii, które się nie sprawdziły. Inne od decyzji, które w swoim czasie wydawały się słuszne, ale źle się zestarzały.
Oto pięć decyzji architektonicznych, które podjąłbym inaczej, gdybym mógł cofnąć czas.
1. Wcześniej postawiłbym na architekturę zdarzeniową
Przez lata budowałem systemy z synchronicznymi wzorcami żądanie-odpowiedź wszędzie. Serwis A wywołuje Serwis B, który wywołuje Serwis C, a jeśli którykolwiek z nich jest wolny lub niedostępny, cały łańcuch się rozpada.
Punkt zwrotny nastąpił, gdy pracowałem nad systemem przetwarzania danych farmaceutycznych, który musiał obsługiwać zgłoszenia regulacyjne. Podejście synchroniczne tworzyło kruchy pipeline, w którym pojedynczy wolny serwis mógł kaskadowo powodować awarie w całym systemie.
Co zrobiłbym inaczej: Zacząłbym od zdarzeń i kolejek komunikatów od pierwszego dnia. Nie wszystko musi być asynchroniczne, ale posiadanie szkieletu zdarzeniowego daje odporność, audytowalność i możliwość dodawania nowych konsumentów bez ingerencji w istniejący kod.
Lekcja: Komunikacja synchroniczna jest domyślna, ale nie powinna być. Domyślnie wybieraj async i przechodź na synchroniczność tylko wtedy, gdy potrzebujesz natychmiastowej odpowiedzi.
2. Zainwestowałbym w obserwowalność, zanim stała się modna
Na początku lat 2000. „monitoring" oznaczał sprawdzenie, czy serwer działa, i ewentualnie obserwowanie zużycia CPU. Logi to było coś, przez co przeszukiwałeś grepem, gdy coś się zepsuło.
Pamiętam incydent produkcyjny, w którym subtelny błąd korupcji danych zajął trzy tygodnie do wyśledzenia, ponieważ nie mieliśmy ustrukturyzowanego logowania, rozproszonego śledzenia ani żadnego sposobu na korelację zdarzeń między serwisami. Trzy tygodnie wpływu na klientów, bo nie widzieliśmy, co nasz system robi.
Co zrobiłbym inaczej: Traktowałbym obserwowalność jako pierwszorzędny aspekt architektury. Ustrukturyzowane logowanie od pierwszego dnia. Rozproszone śledzenie na granicach serwisów. Metryki biznesowe, nie tylko metryki infrastrukturalne.
Lekcja: Nie naprawisz tego, czego nie widzisz. A kiedy uświadamiasz sobie, że potrzebujesz obserwowalności, zwykle jesteś już w trakcie incydentu.
3. Walczyłbym mocniej przeciwko przedwczesnym mikroserwis om
Około 2015 roku mikroserwisy stały się odpowiedzią na każde pytanie. Obserwowałem (i czasem uczestniczyłem w) projektach, które rozbiły monolity na dziesiątki serwisów, zanim miały zespół, narzędzia czy dojrzałość operacyjną, by nimi zarządzać.
Jeden z projektów, przy którym doradzałem, miał 23 mikroserwisy zarządzane przez zespół czterech programistów. Spędzali więcej czasu na debugowaniu komunikacji między serwisami, zarządzaniu deploymentami i radzeniu sobie z transakcjami rozproszonymi niż na tworzeniu funkcjonalności.
Co zrobiłbym inaczej: Zacząłbym od dobrze ustrukturyzowanego monolitu. Wydzielałbym serwisy tylko wtedy, gdy jest ku temu wyraźny powód operacyjny — niezależne skalowanie, niezależna częstotliwość deploymentów lub granice autonomii zespołów. Nie dlatego, że „mikroserwisy to best practice".
Lekcja: Mikroserwisy to wzorzec skalowania organizacyjnego, nie technicznego. Jeśli twój zespół mieści się w jednym pokoju, prawdopodobnie ich nie potrzebujesz.
4. Od początku poważniej potraktowałbym projektowanie baz danych
Na początku kariery traktowałem bazę danych jak głupią warstwę przechowywania. Wrzuć dane, wyciągnij dane. Projekt schematu był sprawą drugorzędną, a normalizacja czymś, co „poprawię później".
„Później" nigdy nie nadeszło. Widziałem systemy, w których jedna źle zaprojektowana tabela stała się wąskim gardłem całej aplikacji. Gdzie brakujące indeksy zamieniały zapytanie trwające 50 ms w 30-sekundowy koszmar. Gdzie brak integralności referencyjnej prowadził do osieroconych rekordów, które przez miesiące fałszowały raporty biznesowe, zanim ktokolwiek to zauważył.
Co zrobiłbym inaczej: Zainwestowałbym czas w porządne modelowanie danych na starcie. Zrozum swoje wzorce zapytań, zanim zaprojektujesz schematy. Używaj ograniczeń i integralności referencyjnej — to nie opcja, to twoja siatka bezpieczeństwa.
Lekcja: Kod twojej aplikacji zostanie przepisany wielokrotnie. Twoje dane przeżyją je wszystkie. Projektuj model danych tak, jakby to była najważniejsza decyzja architektoniczna, jaką podejmiesz — bo prawdopodobnie tak jest.
5. Częściej mówiłbym „nie"
To nie jest decyzja techniczna, ale w gruncie rzeczy jest decyzją architektoniczną w przebraniu. Każde żądanie funkcjonalności, na które pada „tak", dodaje złożoność. Każda integracja dodaje zależność. Każdy „szybki hack" staje się stałą infrastrukturą.
Widziałem bazy kodu, w których architektura degradowała się nie z powodu złych decyzji technicznych, ale z powodu nagromadzenia „tak". Tak dla jednorazowej funkcji eksportu. Tak dla niestandardowego silnika raportowego. Tak dla obsługi tego przestarzałego protokołu „tylko dla tego jednego klienta".
Co zrobiłbym inaczej: Traktowałbym prostotę architektoniczną jako feature. Każdy dodatek powinien uzasadnić swój koszt złożoności. Najlepsza architektura to nie ta, która potrafi wszystko — to ta, która robi właściwe rzeczy dobrze.
Lekcja: Najtrudniejsze słowo w architekturze oprogramowania to „nie". Ale jest też najcenniejsze.
Metalekcja
Patrząc wstecz, te pięć decyzji łączy wspólny wątek: wszystkie dotyczą opierania się złożoności. Architektura zdarzeniowa redukuje złożoność powiązań. Obserwowalność redukuje złożoność debugowania. Unikanie przedwczesnych mikroserwisów redukuje złożoność operacyjną. Dobre projektowanie baz danych redukuje złożoność danych. Mówienie „nie" redukuje złożoność funkcjonalną.
Po 30 latach najważniejsza rzecz, jakiej się nauczyłem, to: celem nie jest zbudowanie najbardziej wyrafinowanego systemu. Celem jest zbudowanie najprostszego systemu, który rozwiązuje problem.
Wszystko inne to ego.