Szczegóły ebooka

Implementowanie Czystej Architektury w Pythonie

Implementowanie Czystej Architektury w Pythonie

Sebastian Buczyński

Ebook

Zaawansowane programowanie zaczyna się tam, gdzie kończy się framework

Praca programisty wydaje się dziś znacznie prostsza niż kilkanaście lat temu. Wydaje się taka, ponieważ ma on dostęp do niezliczonych bibliotek przypisanych do języków programowania. Gdy pojawia się problem, sięga do biblioteki ― i po sprawie. Problem rozwiązany, zgadza się? I tak, i nie. Owszem, w wypadku prostych projektów wystarczy bazowa znajomość zasad programowania, podstawowa znajomość danego języka i wiedza na temat tego, co zawiera konkretna biblioteka. Tak jednak działa to jedynie przy nieskomplikowanych aplikacjach. Bez wątpienia dziś łatwiej zacząć programować i szybciej można uzyskać mierzalne efekty, ale...

...prawdziwe programowanie zaczyna się poziom wyżej. Na etapie większych projektów. Bo duże systemy, niezależnie od języka, w jakim zostały napisane, zawsze są trudne ― zarówno w rozwijaniu, jak i w utrzymaniu. Książka, którą trzymasz w ręku, została napisana ze świadomością tej programistycznej prawdy. Adresowana do średnio zaawansowanych programistów zajmujących się rozwojem aplikacji internetowych, stanowi kompletny przewodnik po implementacji czystej architektury. Znajdziesz tu także opisy wielu technik, które pomogą Ci zapanować nad projektami rozwijanymi od dłuższego czasu, takich jak strategia testowania czy modularyzacja. Dzięki ich opanowaniu będzie Ci o wiele łatwiej dbać o poprawność funkcjonowania systemów, nad którymi sprawujesz programistyczną pieczę.

1. WPROWADZENIE

  • 1.1. Era narzędzi
  • 1.2. Dla kogo jest ta książka?
  • 1.3. Co znajdziesz w książce?

2. PODSTAWY CZYSTEJ ARCHITEKTURY

  • 2.1. Po co to wszystko?
  • 2.2. System płytki kontra system głęboki
    • 2.2.1. CRUD, czyli system płytki
    • 2.2.2. System głęboki
  • 2.3. Założenia czystej architektury
    • 2.3.1. Niezależność od frameworków
    • 2.3.2. Wysoka testowalność
    • 2.3.3. Niezależność od API i interfejsu użytkownika
    • 2.3.4. Niezależność od bazy danych
    • 2.3.5. Niezależność od firm trzecich
    • 2.3.6. Elastyczność
    • 2.3.7. Rozszerzalność
  • 2.4. Warstwy, czyli horyzontalna organizacja kodu
    • 2.4.1. Świat zewnętrzny
    • 2.4.2. Infrastruktura
    • 2.4.3. Aplikacja
    • 2.4.4. Domena
    • 2.4.5. Zasada zależności
    • 2.4.6. Granice
  • 2.5. Podsumowanie

3. WZORCOWA IMPLEMENTACJA

  • 3.1. Oznajmienie
  • 3.2. Przepływ sterowania w czystej architekturze
  • 3.3. Wymagania biznesowe
  • 3.4. Implementacja
    • 3.4.1. Diagram sekwencji
    • 3.4.2. Granica wejściowa (input boundary)
    • 3.4.3. Granica wyjściowa (output boundary)
    • 3.4.4. Prezenter (presenter)
    • 3.4.5. Model widoku (view model)
    • 3.4.6. Przypadek użycia (use case)
    • 3.4.7. Interfejs dostępu do danych (data access interface)
    • 3.4.8. Dostęp do danych (data access)
    • 3.4.9. Encja oferty (bid)
    • 3.4.10. Encja aukcji (auction)
  • 3.5. Podsumowanie

4. MODYFIKACJE CZYSTEJ ARCHITEKTURY

  • 4.1. Dylemat prezentera
  • 4.2. Pozbywamy się granicy wejściowej
  • 4.3. Alternatywne podejścia do projektowania przypadków użycia
    • 4.3.1. Fasada
    • 4.3.2. Mediator pomiędzy wejściowym DTO a przypadkiem użycia
  • 4.4. Użycie modeli bazodanowych jako encji
  • 4.5. Podsumowanie

5. WSTRZYKIWANIE ZALEŻNOŚCI

  • 5.1. Czym są zależności?
  • 5.2. Wszędobylskie abstrakcje i klasy
  • 5.3. Abstrakcje w czystej architekturze
  • 5.4. Odwrócenie sterowania a zależności
  • 5.5. Kontener IoC kontra service locator
  • 5.6. Wstrzykiwanie zależności kontra konfiguracja
  • 5.7. Podsumowanie

6. CQRS

  • 6.1. Wstęp
  • 6.2. Co to ma wspólnego z czystą architekturą?
  • 6.3. Osobny stos odczytu - dlaczego?
  • 6.4. Osobny stos odczytu - jak?
    • 6.4.1. Zapytanie jako DTO
    • 6.4.2. Zapytania jako osobne klasy
    • 6.4.3. Fasada modelu do odczytu
  • 6.5. CQRS kontra REST API
  • 6.6. CQRS kontra GraphQL
  • 6.7. Podsumowanie

7. OSTRE GRANICE

  • 7.1. Słowo o złożoności
  • 7.2. Dwa światy
  • 7.3. Granica pomiędzy warstwą aplikacji a światem zewnętrznym
  • 7.4. Pisanie wejściowego DTO
  • 7.5. Value objects
  • 7.6. Podsumowanie

8. STUDIUM PRZYPADKU - PLATFORMA AUKCYJNA

  • 8.1. Jak pracujemy?
  • 8.2. Jak zacząć, czyli chodzący szkielet
  • 8.3. Nasz chodzący szkielet
  • 8.4. Przypadek użycia dla składania oferty na aukcji
    • 8.4.1. Nazewnictwo
    • 8.4.2. Argumenty
    • 8.4.3. Wyjście
    • 8.4.4. Testy
  • 8.5. Encje aukcji i oferty
    • 8.5.1. Nazewnictwo
    • 8.5.2. Value objects jako identyfikatory
    • 8.5.3. Implementacja
    • 8.5.4. Testy jednostkowe
    • 8.5.5. Implementacja - ciąg dalszy
  • 8.6. Abstrakcyjne repozytorium
    • 8.6.1. Nazewnictwo
    • 8.6.2. Implementacja
  • 8.7. Repozytorium
    • 8.7.1. Nazewnictwo
    • 8.7.2. Implementacja działająca w pamięci
    • 8.7.3. Rozwijanie implementacji pod osłoną TDD
  • 8.8. Kończymy przypadek użycia - składanie oferty
    • 8.8.1. Wstrzykiwanie zależności
    • 8.8.2. Sprawiamy, że pierwszy sensowny test przechodzi
    • 8.8.3. Refaktoryzacja
  • 8.9. Organizacja kodu
    • 8.9.1. Jak można ułożyć kod w Pythonie?
    • 8.9.2. Organizujemy kod projektu
    • 8.9.3. Organizujemy kod warstwy infrastruktury
    • 8.9.4. Łączymy wszystko razem w komponencie main
    • 8.9.5. Wystawiamy API
  • 8.10. Finalizujemy aukcję w kolejnym przypadku użycia
    • 8.10.1. Zarys przypadku użycia i wejściowe DTO
    • 8.10.2. Rozszerzamy encję, by spełnić nowe wymagania
    • 8.10.3. Skoro encje nie powinny mieć żadnych zależności, to czy mogą pytać o czas?
    • 8.10.4. Wprowadzamy port dla płatności
    • 8.10.5. Implementujemy adapter
    • 8.10.6. Obsługa błędów kontra zasada zależności
    • 8.10.7. A co, gdybyśmy chcieli dodać zapamiętywanie karty płatniczej?
    • 8.10.8. Jak żyć, gdy adapter rośnie?
    • 8.10.9. Bramka płatności ma już SDK. Nie możemy go po prostu użyć?
  • 8.11. Przypadek użycia - rozpoczynanie nowej aukcji
    • 8.11.1. Skąd się biorą nowe aukcje?
    • 8.11.2. Encja aukcji i jej opis w jednym obiekcie - za i przeciw
    • 8.11.3. Wprowadzamy deskryptor
    • 8.11.4. Repozytorium z interfejsem kolekcji
    • 8.11.5. Które repozytorium wybrać?
  • 8.12. Operacje odczytu danych
    • 8.12.1. Podejście z przypadkami użycia
    • 8.12.2. CQRS na ratunek
    • 8.12.3. Zapytania jako klasa
    • 8.12.4. Model do odczytu
    • 8.12.5. Podsumowanie operacji odczytujących dane
  • 8.13. Odwracamy kontrolę za pomocą zdarzeń
    • 8.13.1. Przykład - wysyłka e-maili
    • 8.13.2. Techniki odwracania kontroli
    • 8.13.3. Implementacja zdarzeń
    • 8.13.4. Skąd wziąć szynę zdarzeń?
    • 8.13.5. Jak wydostać zdarzenia z encji?
    • 8.13.6. Encja gromadzi zdarzenia, które potem publikuje repozytorium
    • 8.13.7. Encja zwraca zdarzenia z metod, które zmieniają jej stan
    • 8.13.8 Testowanie encji, które zwracają zdarzenia
    • 8.13.9. Subskrybowanie się na zdarzenia
    • 8.13.10. Zdarzenia kontra transakcje kontra efekty uboczne
    • 8.13.11. Niezawodne publikowanie zdarzeń - outbox pattern
    • 8.13.12. Wprowadzamy jednostkę pracy
    • 8.13.13. Czas życia jednostki pracy
    • 8.13.14. Relacja pomiędzy jednostką pracy a szyną zdarzeń
  • 8.14. Radzimy sobie z innymi przekrojowymi zagadnieniami
    • 8.14.1. Konfiguracja
    • 8.14.2. Walidacja
    • 8.14.3. Synchronizacja
  • 8.15. Podsumowanie

9. MODULARNOŚĆ

  • 9.1. Ciężar sukcesu - rozrost i ciągłe zmiany
  • 9.2. Komponenty i kohezja
  • 9.3. Organizacja kodu według komponentu
  • 9.4. Komponenty i swoboda architektoniczna
  • 9.5. Komponenty kontra mikroserwisy
  • 9.6. Komponenty a użytkownik
  • 9.7. Komponenty a bounded context
  • 9.8. Komponenty - implementacja
  • 9.9. Zależności między komponentami
    • 9.9.1. Oddzielne drogi
    • 9.9.2. Bezpośrednia zależność - oba komponenty implementują czystą architekturę
    • 9.9.3. Niebezpośrednia zależność - oba komponenty implementują czystą architekturę
    • 9.9.4. Zależność, gdy jeden z komponentów nie implementuje czystej architektury
    • 9.9.5. Odmiany integracji za pomocą zdarzeń
    • 9.9.6. Zależności między komponentami - podsumowanie
  • 9.10. Studium przypadku - platforma aukcyjna
    • 9.10.1. Odkrywamy komponenty
    • 9.10.2. Komponenty platformy aukcyjnej
    • 9.10.3. Co komponent wystawia na zewnątrz?
    • 9.10.4. Tam, gdzie wszystko składa się w całość - komponent main
    • 9.10.5. Korzystamy z komponentu main do uruchomienia aplikacji
    • 9.10.6. Jedna architektura dla wszystkich komponentów - czy to możliwe?
    • 9.10.7. Zależności pomiędzy komponentami
    • 9.10.8. Integrowanie komponentów za pomocą zdarzeń
    • 9.10.9. Wewnętrzna obsługa zdarzeń w tym samym komponencie
    • 9.10.10. Integracja różnych komponentów za pomocą zdarzeń - prosty przypadek
    • 9.10.11. Integracja różnych komponentów za pomocą zdarzeń - złożony przypadek
    • 9.10.12. Inne ciekawe zastosowania menadżera procesu
    • 9.10.13. Menadżer procesu kontra wyścigi
  • 9.11. Podsumowanie

10. TESTOWANIE

  • 10.1. Strategia testowania i odmiany funkcji
    • 10.1.1. Piramida testów - mit czy jedyna słuszna droga?
    • 10.1.2. Rodzaje testów
    • 10.1.3. Jak przetestować przeglądarkę do bazy danych?
    • 10.1.4. Jak przetestować proxy?
    • 10.1.5. Jak przetestować system głęboki?
  • 10.2. Odkrywamy testowanie jednostkowe na nowo
    • 10.2.1. Ile musi wiedzieć test?
  • 10.3. Testowanie stanu kontra testowanie interakcji
    • 10.3.1. Rodzaje weryfikacji
    • 10.3.2. Niebezpieczeństwa związane z inspekcją stanu
    • 10.3.3. Niebezpieczeństwa związane ze sprawdzaniem interakcji
    • 10.3.4. Stuby kontra mocki
    • 10.3.5. Rodzaje obiektów dublerów
  • 10.4. Testujemy cały komponent jednostkowo
    • 10.4.1. Ustawiamy komponent w pożądanym stanie
    • 10.4.2. Wywołujemy akcję na komponencie
    • 10.4.3. Weryfikujemy rezultat akcji na poziomie komponentu
    • 10.4.4. Radzimy sobie z zależnościami w postaci portów i repozytoriów
  • 10.5. Podsumowanie

11. ZAKOŃCZENIE

  • 11.1. Co dalej?

12. SUPLEMENT A: MIGRACJA Z PROJEKTU ODZIEDZICZONEGO

  • 12.1. Czy powinno się to robić?
  • 12.2. Jak to zrobić?
  • 12.3. "Nie mogę przestać dostarczać nowych funkcji!"

13. SUPLEMENT B: WPROWADZENIE DO EVENT SOURCING

  • 13.1. Co to jest event sourcing?
  • 13.2. Agregat z event sourcing kontra agregat z domain-driven design
  • 13.3. Prosty przykład agregatu
    • 13.3.1. Zamówienie jako encja
    • 13.3.2. Istotne zmiany zamówienia w formie zdarzeń
    • 13.3.3. Uwaga na te zdarzenia!
    • 13.3.4. Zamówienie jako agregat
    • 13.3.5. Testowanie agregatów
  • 13.4. Persystencja
    • 13.4.1. Nowe zdarzenia są dołączane na koniec strumienia zdarzeń
    • 13.4.2. Pobieranie strumienia zdarzeń
    • 13.4.3. Dopisywanie nowych zdarzeń do strumienia
    • 13.4.4. Wybór bazy danych - podsumowanie wymagań
    • 13.4.5. Przykładowa implementacja z użyciem PostgreSQL
    • 13.4.6. Użycie event store
    • 13.4.7. Co robić, gdy wykryjemy wyścig?
    • 13.4.8. Użycie repozytorium do ukrycia event store
    • 13.4.9. Migawki stanu agregatu
  • 13.5. Projekcje
  • 13.6. Event sourcing w aplikacji składającej się z komponentów
    • 13.6.1. Event sourcing to szczegół implementacyjny komponentu
    • 13.6.2. Stosuj zdarzenia domenowe na potrzeby integracji
  • 13.7. Podsumowanie

BIBLIOGRAFIA

  • Tytuł: Implementowanie Czystej Architektury w Pythonie
  • Autor: Sebastian Buczyński
  • ISBN: 978-83-283-9752-1, 9788328397521
  • Data wydania: 2022-06-14
  • Format: Ebook
  • Identyfikator pozycji: imczar
  • Wydawca: Helion