paint-brush
Jak radzić sobie ze złożonością podczas projektowania systemów oprogramowaniaprzez@fairday
64,467 odczyty
64,467 odczyty

Jak radzić sobie ze złożonością podczas projektowania systemów oprogramowania

przez Aleksei23m2024/02/05
Read on Terminal Reader
Read this story w/o Javascript

Za długo; Czytać

Złożoność jest wrogiem! Nauczmy się sobie z nią radzić!
featured image - Jak radzić sobie ze złożonością podczas projektowania systemów oprogramowania
Aleksei HackerNoon profile picture

O co w tym wszystkim chodzi?

Każdego dnia, w każdej chwili naszej kariery inżynierskiej napotykamy wiele różnych problemów o różnym stopniu złożoności i sytuacji, w których musimy podjąć decyzję lub odłożyć ją z powodu braku danych. Za każdym razem, gdy budujemy nowe usługi, konstruujemy infrastrukturę, a nawet tworzymy procesy rozwojowe, dotykamy ogromnego świata różnych wyzwań.


Trudno, a może nawet niemożliwe jest wymienienie wszystkich problemów. Na niektóre z nich natkniesz się tylko wtedy, gdy pracujesz w określonej niszy. Z drugiej strony jest wiele, które wszyscy musimy zrozumieć, jak rozwiązać, ponieważ są kluczowe dla budowania systemów informatycznych. Z dużym prawdopodobieństwem natkniesz się na nie we wszystkich projektach.


W tym artykule podzielę się swoimi doświadczeniami dotyczącymi niektórych problemów, na jakie natknąłem się podczas tworzenia programów komputerowych.

Czym jest problem przekrojowy?

Jeśli zajrzymy do Wikipedii, znajdziemy następującą definicję


W zorientowanym aspektowo rozwoju oprogramowania, kwestie przekrojowe to aspekty programu, które wpływają na kilka modułów, bez możliwości ich uwzględnienia w żadnym z nich. Kwestie te często nie mogą być czysto rozłożone od reszty systemu zarówno w projekcie, jak i implementacji, i mogą skutkować rozproszeniem (duplikacją kodu), splątaniem (znacznymi zależnościami między systemami) lub jednym i drugim.


Opis ten doskonale opisuje istotę sprawy, ale chciałbym go nieco rozszerzyć i uprościć:

Zagadnienie przekrojowe to koncepcja lub komponent systemu/organizacji, który wpływa na wiele innych części (lub „przecina się”).


Najlepszymi przykładami takich obaw są architektura systemu, rejestrowanie, bezpieczeństwo, zarządzanie transakcjami, telemetria, projektowanie baz danych i wiele innych. Wiele z nich omówimy szczegółowo w dalszej części artykułu.


Na poziomie kodu, kwestie przekrojowe są często implementowane przy użyciu technik takich jak Aspect-Oriented Programming (AOP) , gdzie kwestie te są modularne do oddzielnych komponentów, które można stosować w całej aplikacji. Dzięki temu logika biznesowa pozostaje odizolowana od tych kwestii, co sprawia, że kod jest bardziej czytelny i łatwiejszy w utrzymaniu.

Klasyfikacja aspektów

Istnieje wiele możliwych sposobów klasyfikowania aspektów poprzez segmentowanie ich według różnych właściwości, takich jak zakres, rozmiar, funkcjonalność, ważność, cel i inne, ale w tym artykule zamierzam użyć prostej klasyfikacji zakresu. Mam na myśli, gdzie skierowany jest ten konkretny aspekt, czy jest to cała organizacja, konkretny system, czy konkretny element tego systemu.


Zatem podzielę aspekty na makro i mikro .


Przez aspekt makro rozumiem głównie względy, które bierzemy pod uwagę w odniesieniu do całego systemu, takie jak wybrana architektura systemu i jej projekt (monolityczna, mikrousługi, architektura zorientowana na usługi), stos technologiczny, struktura organizacyjna itd. Aspekty makro odnoszą się głównie do decyzji strategicznych i decyzji podejmowanych na wysokim szczeblu.


Tymczasem aspekt Micro jest znacznie bliższy poziomowi kodu i rozwojowi. Na przykład, który framework jest używany do interakcji z bazą danych, struktura folderów i klas projektu, a nawet konkretne wzorce projektowania obiektów.


Mimo że klasyfikacja ta nie jest idealna, pomaga ona zrozumieć potencjalne problemy oraz znaczenie i wpływ rozwiązań, które stosujemy w ich rozwiązywaniu.


W tym artykule skupię się przede wszystkim na aspektach makro.

Aspekty makro

Struktura organizacyjna

Kiedy dopiero zaczynałem uczyć się o architekturze oprogramowania, przeczytałem wiele interesujących artykułów o prawie Conwaya i jego wpływie na strukturę organizacyjną. Zwłaszcza ten . Tak więc to prawo stwierdza, że


Każda organizacja projektująca system (rozumiany szeroko) stworzy projekt, którego struktura będzie kopią struktury komunikacyjnej danej organizacji.


Zawsze wierzyłem, że ta koncepcja jest rzeczywiście bardzo uniwersalna i odzwierciedla Złotą Zasadę.


Następnie zacząłem uczyć się podejścia Erica Evansa do projektowania zorientowanego na domenę (DDD) w zakresie modelowania systemów. Eric Evans podkreśla znaczenie identyfikacji ograniczonego kontekstu. Koncepcja ta polega na podzieleniu złożonego modelu domeny na mniejsze, bardziej zarządzalne sekcje, z których każda ma swój własny, ograniczony zestaw wiedzy. Podejście to wspomaga skuteczną komunikację w zespole, ponieważ zmniejsza potrzebę rozległej wiedzy na temat całej domeny i minimalizuje przełączanie kontekstu, dzięki czemu rozmowy stają się bardziej wydajne. Przełączanie kontekstu jest najgorszą i najbardziej zasobożerną rzeczą na świecie. Nawet komputery mają z tym problem. Chociaż mało prawdopodobne jest osiągnięcie całkowitego braku przełączania kontekstu, uważam, że właśnie do tego powinniśmy dążyć.


Fantasy about keeping in mind a lot of bounded contexts

Wracając do prawa Conwaya, znalazłem w nim kilka problemów.


Pierwszy problem, na jaki natrafiłem w przypadku prawa Conwaya, które sugeruje, że projekt systemu odzwierciedla strukturę organizacyjną, to potencjał tworzenia złożonych i kompleksowych kontekstów ograniczonych. Ta złożoność pojawia się, gdy struktura organizacyjna nie jest zgodna z granicami domeny, co prowadzi do kontekstów ograniczonych, które są silnie współzależne i obciążone informacjami. Prowadzi to do częstego przełączania kontekstów przez zespół programistów.


Innym problemem jest to, że terminologia organizacyjna przecieka na poziom kodu. Gdy struktury organizacyjne ulegają zmianie, konieczne są modyfikacje bazy kodu, co pochłania cenne zasoby.


Tak więc, podążanie za Inverse Conway Maneuver pomaga budować system i organizację, które zachęcają do pożądanej architektury oprogramowania. Warto jednak zauważyć, że to podejście nie sprawdzi się zbyt dobrze w już uformowanej architekturze i strukturach, ponieważ zmiany na tym etapie są przedłużane, ale jest wyjątkowo skuteczne w startupach, ponieważ szybko wprowadzają wszelkie zmiany.

Duża Kula Błota

Ten wzorzec lub „antywzorzec” napędza budowanie systemu bez żadnej architektury. Nie ma żadnych reguł, żadnych granic ani żadnej strategii, jak kontrolować nieuchronnie rosnącą złożoność. Złożoność jest najgroźniejszym wrogiem w podróży budowania systemów oprogramowania.


Entertaining illustration made by ChatGPT

Aby uniknąć stworzenia takiego systemu, musimy przestrzegać określonych zasad i ograniczeń.

Architektura systemu

Istnieje niezliczona ilość definicji architektury oprogramowania. Wiele z nich mi się podoba, ponieważ obejmują różne jej aspekty. Jednak, aby móc rozumować o architekturze, musimy naturalnie uformować niektóre z nich w naszych umysłach. I warto zauważyć, że ta definicja może ewoluować. Więc przynajmniej na razie mam następujący opis dla siebie.


Architektura oprogramowania dotyczy decyzji i wyborów, które podejmujesz każdego dnia i które mają wpływ na tworzony system.


Aby podejmować decyzje, musisz mieć w „torbie” zasady i wzorce rozwiązywania pojawiających się problemów, ważne jest również stwierdzenie, że zrozumienie wymagań jest kluczem do zbudowania tego, czego potrzebuje firma. Jednak czasami wymagania nie są przejrzyste lub nawet nie są zdefiniowane, w takim przypadku lepiej poczekać, aby uzyskać więcej wyjaśnień lub polegać na swoim doświadczeniu i zaufać swojej intuicji. Tak czy inaczej, nie możesz podejmować właściwych decyzji, jeśli nie masz zasad i wzorców, na których możesz polegać. Właśnie tutaj przechodzę do definicji stylu architektury oprogramowania.


Styl architektury oprogramowania to zbiór zasad i wzorców, które określają sposób tworzenia oprogramowania.


Istnieje wiele różnych stylów architektonicznych, skupionych na różnych aspektach planowanej architektury, a stosowanie wielu z nich na raz jest sytuacją normalną.


Na przykład takie jak:

  1. Architektura monolityczna

  2. Projektowanie zorientowane na domenę

  3. Oparty na komponentach

  4. Mikrousługi

  5. Rury i filtry

  6. Zdarzeniowe

  7. Mikrojądro

  8. Zorientowany na usługi


i tak dalej…


Oczywiście, mają swoje zalety i wady, ale najważniejszą rzeczą, której się nauczyłem, jest to, że architektura ewoluuje stopniowo, w zależności od rzeczywistych problemów. Rozpoczęcie od architektury monolitycznej to świetny wybór, aby zmniejszyć złożoność operacyjną, bardzo prawdopodobne, że ta architektura będzie odpowiadać Twoim potrzebom nawet po osiągnięciu etapu dopasowania produktu do rynku (PMI) podczas tworzenia produktu. W skali możesz rozważyć przejście na podejście oparte na zdarzeniach i mikrousługi w celu osiągnięcia niezależnego wdrożenia, heterogenicznego środowiska stosu technologicznego i mniej sprzężonej architektury (i mniej przejrzystej w międzyczasie ze względu na naturę podejść opartych na zdarzeniach i pub-sub, jeśli zostaną przyjęte). Prostota i wydajność są blisko siebie i mają na siebie duży wpływ. Zazwyczaj skomplikowane architektury wpływają na szybkość rozwoju nowych funkcji, wspierają i utrzymują istniejące oraz stanowią wyzwanie dla naturalnej ewolucji systemu.


Jednakże złożone systemy często wymagają złożonej i kompleksowej architektury, co jest nieuniknione.


Słusznie, to bardzo, bardzo szeroki temat i jest wiele świetnych pomysłów na to, jak ustrukturyzować i zbudować systemy dla naturalnej ewolucji. Na podstawie mojego doświadczenia opracowałem następujące podejście:

  1. Prawie zawsze zaczyna się od monolitycznego stylu architektury, ponieważ eliminuje on większość problemów wynikających z natury rozproszonych systemów. Ma również sens podążanie za modularnym monolitem, aby skupić się na budowaniu komponentów z wyraźnymi granicami. Zastosowanie podejścia opartego na komponentach mogłoby pomóc im komunikować się ze sobą za pomocą zdarzeń, ale posiadanie bezpośrednich wywołań (zwanych również RPC) upraszcza sprawy na początku. Ważne jest jednak śledzenie zależności między komponentami, ponieważ jeśli komponent A wie dużo o komponencie B, być może ma sens połączenie ich w jeden.
  2. Gdy zbliżysz się do sytuacji, w której konieczne będzie skalowanie rozwoju i systemu, możesz rozważyć zastosowanie wzorca Stanglera , który polega na stopniowym wyodrębnianiu komponentów, które należy wdrożyć niezależnie, a nawet skalować zgodnie ze szczególnymi wymaganiami.
  3. Teraz, jeśli masz jasną wizję przyszłości, co jest niewiarygodnym szczęściem, możesz zdecydować się na pożądaną architekturę. W tej chwili możesz zdecydować się na przejście na architekturę mikrousług, stosując również podejścia Orchestration i Choreography, włączając wzorzec CQRS dla niezależnych skalowanych operacji zapisu i odczytu, a nawet decydując się na architekturę monolityczną, jeśli spełnia ona Twoje potrzeby.


Ważne jest również zrozumienie liczb i wskaźników, takich jak DAU (liczba aktywnych użytkowników dziennie), MAU (liczba aktywnych użytkowników miesięcznie), RPC (liczba żądań na sekundę) i TPC (liczba transakcji na sekundę), ponieważ może to pomóc w podejmowaniu decyzji, gdyż architektura dla 100 aktywnych użytkowników i 100 milionów aktywnych użytkowników jest inna.


Na koniec chciałbym powiedzieć, że architektura ma znaczący wpływ na sukces produktu. Źle zaprojektowana architektura produktów jest wymagana w skalowaniu, co bardzo prawdopodobnie prowadzi do porażki, ponieważ klienci nie będą czekać, aż skalujesz system, wybiorą konkurenta, więc musimy wyprzedzać potencjalne skalowanie. Chociaż przyznaję, że czasami nie może to być podejście szczupłe, chodzi o to, aby mieć skalowalny, ale nie już skalowany system. Z drugiej strony, posiadanie bardzo skomplikowanego i już skalowanego systemu bez klientów lub planów zdobycia wielu z nich będzie Cię kosztować pieniądze w Twojej firmie za nic.

Wybór stosu technologicznego

Wybór zestawu technologii jest również decyzją na poziomie makro, ponieważ wpływa na zatrudnianie, perspektywy naturalnej ewolucji systemu, skalowalność i wydajność systemu.


Poniżej znajduje się lista podstawowych kwestii, które należy wziąć pod uwagę przy wyborze zestawu technologii:

  • Wymagania i złożoność projektu. Na przykład, prosta aplikacja internetowa może zostać zbudowana przy użyciu frameworka Blazor, jeśli Twoi programiści mają z nim doświadczenie, ale ze względu na brak dojrzałości WebAssembly, wybór React i Typescript dla długoterminowego sukcesu może być lepszą decyzją
  • Potrzeby skalowalności i wydajności. Jeśli przewidujesz otrzymywanie dużej ilości ruchu, wybór ASP.NET Core zamiast Django może być mądrym wyborem ze względu na jego lepszą wydajność w obsłudze równoczesnych żądań. Jednak ta decyzja zależy od skali ruchu, której się spodziewasz. Jeśli musisz zarządzać potencjalnie miliardami żądań z niskim opóźnieniem, obecność Garbage Collection może być wyzwaniem.
  • Zatrudnianie, czas rozwoju i koszt. W większości przypadków są to czynniki, o które musimy dbać. Czas wprowadzenia na rynek, koszty utrzymania i stabilność zatrudnienia napędzają potrzeby Twojej firmy bez przeszkód.
  • Wiedza i zasoby zespołu. Zestaw umiejętności Twojego zespołu programistów jest czynnikiem krytycznym. Zazwyczaj bardziej efektywne jest korzystanie z technologii, z którymi Twój zespół jest już zaznajomiony, chyba że istnieje silny powód, aby zainwestować w naukę nowego stosu.
  • Dojrzałość. Silna społeczność i bogaty ekosystem bibliotek i narzędzi mogą znacznie ułatwić proces rozwoju. Popularne technologie często mają lepsze wsparcie społeczności, co może być nieocenione w rozwiązywaniu problemów i znajdowaniu zasobów. Dzięki temu możesz zaoszczędzić zasoby i skupić się głównie na produkcie.
  • Długoterminowa konserwacja i wsparcie. Weź pod uwagę długoterminową żywotność technologii. Technologie, które są szeroko przyjęte i obsługiwane, rzadziej stają się przestarzałe i zazwyczaj otrzymują regularne aktualizacje i ulepszenia.


Jak korzystanie z wielu zestawów technologii może wpłynąć na rozwój firmy?

Z jednej strony wprowadzenie jednego stosu więcej może zwiększyć skalę zatrudnienia, ale z drugiej strony wiąże się z dodatkowymi kosztami utrzymania, ponieważ musisz obsługiwać oba stosy. Tak więc, jak powiedziałem wcześniej, moim zdaniem, tylko dodatkowa potrzeba powinna być argumentem za włączeniem większej liczby stosów technologicznych.


Na czym jednak polega zasada wyboru najlepszego narzędzia do rozwiązania konkretnego problemu?

Czasami nie ma innego wyjścia, jak tylko sięgnąć po nowe narzędzia w celu rozwiązania konkretnego problemu, opierając się na powyższych założeniach. W takich przypadkach warto wybrać najlepsze rozwiązanie.


Tworzenie systemów bez wysokiego sprzężenia z konkretną technologią może być wyzwaniem. Mimo to pomocne jest dążenie do stanu, w którym system nie jest ściśle sprzężony z technologią i nie umrze, jeśli jutro konkretny framework lub narzędzie stanie się podatne na ataki lub nawet przestarzałe.


Innym ważnym czynnikiem jest zależność od oprogramowania open source i własnościowego. Oprogramowanie własnościowe daje mniejszą elastyczność i możliwość dostosowania. Nadal najbardziej niebezpiecznym czynnikiem jest uzależnienie od dostawcy, w którym stajesz się zależny od produktów, cen, warunków i planu działania dostawcy. Może to być ryzykowne, jeśli dostawca zmieni kierunek, podniesie ceny lub zaprzestanie produkcji produktu. Oprogramowanie open source zmniejsza to ryzyko, ponieważ nie kontroluje go pojedyncza jednostka. Wyeliminowanie pojedynczego punktu awarii na wszystkich poziomach jest kluczem do budowania niezawodnych systemów wzrostu.

Pojedynczy punkt awarii (SPOF)

Pojedynczy punkt awarii (SPOF) odnosi się do dowolnej części systemu, która w przypadku awarii spowoduje, że cały system przestanie działać. Eliminacja SPOF-ów na wszystkich poziomach jest kluczowa dla każdego systemu wymagającego wysokiej dostępności. Wszystko, w tym wiedza, personel, komponenty systemu, dostawcy chmury i kable internetowe, może zawieść.


Istnieje kilka podstawowych technik, które możemy zastosować w celu wyeliminowania pojedynczych punktów awarii:

  1. Nadmiarowość. Wdrażaj nadmiarowość dla krytycznych komponentów. Oznacza to posiadanie komponentów zapasowych, które mogą przejąć zadania, jeśli główny komponent ulegnie awarii. Nadmiarowość można stosować w różnych warstwach systemu, w tym w sprzęcie (serwery, dyski), sieci (łącza, przełączniki) i oprogramowaniu (bazy danych, serwery aplikacji). Jeśli hostujesz wszystko u jednego dostawcy chmury i nawet masz tam kopie zapasowe, rozważ utworzenie regularnej dodatkowej kopii zapasowej u innego, aby zmniejszyć utracone koszty w przypadku katastrofy.
  2. Centra danych. Rozłóż swój system w wielu lokalizacjach fizycznych, takich jak centra danych lub regiony chmurowe. Takie podejście chroni system przed awariami specyficznymi dla danej lokalizacji, takimi jak przerwy w dostawie prądu lub klęski żywiołowe.
  3. Failover. Zastosuj podejście failover dla wszystkich komponentów (DNS, CDN, Load balancers, Kubernetes, API Gateways i Databases). Ponieważ problemy mogą pojawić się nieoczekiwanie, kluczowe jest posiadanie planu zapasowego, aby w razie potrzeby szybko zastąpić dowolny komponent jego klonem.
  4. Usługi o wysokiej dostępności. Upewnij się, że Twoje usługi są zbudowane tak, aby były skalowalne poziomo i wysoce dostępne od samego początku, przestrzegając następujących zasad:
    • Praktykuj bezstanowość usług i unikaj przechowywania sesji użytkowników w pamięci podręcznej. Zamiast tego używaj rozproszonego systemu pamięci podręcznej, takiego jak Redis.
    • Przy opracowywaniu logiki unikaj polegania na chronologicznej kolejności odbioru wiadomości.
    • Minimalizuj zmiany powodujące przerwy, aby zapobiec zakłóceniom dla konsumentów API. Jeśli to możliwe, wybieraj zmiany zgodne z poprzednimi wersjami. Weź również pod uwagę koszty, ponieważ czasami wdrożenie zmiany powodującej przerwy może być bardziej opłacalne.
    • Włącz wykonywanie migracji do procesu wdrażania.
    • Opracuj strategię obsługi równoczesnych żądań.
    • Wdrożenie wykrywania, monitorowania i rejestrowania usług w celu zwiększenia niezawodności i możliwości obserwacji.
    • Opracuj idempotentną logikę biznesową, pamiętając, że awarie sieci są nieuniknione.
  5. Przegląd zależności. Regularnie przeglądaj i minimalizuj zewnętrzne zależności. Każda zewnętrzna zależność może wprowadzać potencjalne SPOF-y, dlatego ważne jest zrozumienie i ograniczenie tych ryzyk.
  6. Regularne dzielenie się wiedzą. Nigdy nie zapominaj o znaczeniu szerzenia wiedzy w swojej organizacji. Ludzie mogą być nieprzewidywalni, a poleganie na jednej osobie jest ryzykowne. Zachęcaj członków zespołu do digitalizacji swojej wiedzy poprzez dokumentację. Uważaj jednak na nadmierną dokumentację. Wykorzystaj różne narzędzia AI, aby uprościć ten proces.

Wniosek

W tym artykule omówiliśmy kilka kluczowych aspektów makroekonomicznych i sposoby radzenia sobie z ich złożonością.


Dziękuję za przeczytanie! Do zobaczenia następnym razem!