Omówienie podstawowych wzorców projektowych oraz zasad architektonicznych na przykładzie aplikacji ASP.NET, ale w większości niezależnych od stosowanej technologii. Najważniejsze założenia domain-driven design, SOLID principles, itp.
2. Keep It Simple Stupid (KISS)
Don’t Repeat Yourself (DRY)
Tell, Don’t Ask (reguła Hollywood)
Wydajemy polecenie obiektom, zamiast pytać o ich stan i wykonywać akcję
You Ain’t Gonna Need It (YAGNI) – tylko to, co niezbędne
Separation of Concerns (SoC)
Główne zasady
3. Single Responsibility Principle (SRP)
Nie monolityczna konstrukcja (scyzoryk), 1 powód do zmiany stanu
Open-Closed Principle (OCP)
Klasy powinny być otwarte na rozszerzenia, ale zamknięte na zmiany
(brak problemów podczas modyfikacji klas bazowych)
Liskov Substitution Principle (LSP)
Możliwość podmienienia każdej dziedziczącej klasy z klasą bazową, bez wpływu na działanie
Interface Segregation Principle (ISP)
Grupowanie interfejsu na mniejsze
Dependency Inversion Principle (DIP)
Zależność od abstrakcji, nie konkretnej implementacji
Inversion of Control (IoC) – kontener / framework wstrzykujący konkretną implementację (odwrócenie
kontroli)
Dependency Injection (DI) – wstrzykiwanie implementacji przez konstruktor, metodę lub właściwość
Zasady SOLID
4. Test-driven Development (TDD)
Zaczynamy od pisania testu nieistniejącej jeszcze funkcjonalności
Red-Green (od narzędzi)
Domain-driven Design (DDD)
Wierne modelowanie logiki biznesowej w kodzie
Ten sam język, używany przez biznes
Behavior-driven Design (BDD)
Evolucja TDD połączona z DDD
Dokumentacja, specyfikacje, testy sprawdzające zgodność z
założeniami
Inne praktyki
6. Model
Obiekty domenowe, logika, relacje
Interfejsy potrzebne do zapisu (Repository)
Brak referencji do infrastruktury pod spodem
Repository
Referencja do modeli
Implementacja interfejsów repozytorium z projektu modelu
Application Service
Gateway do aplikacji
Komunikacja z frontendem przez DTO
ViewModele
Web
Komunikacja wyłącznie z Application Service
W odpowiedzi ViewModele
Podział na projekty
8. Proste, proceduralne podejście
Np. statyczna klasa
Proste, dobre dla małych aplikacji
Mniej wymagające dla programistów
Trudne w utrzymaniu, kiedy aplikacja się rozrasta
Transaction Script
9. Efektywne, kiedy struktura bazy odzwierciedla model
biznesowy
Np. blog
Każdy obiekt odpowiada za zapis swojego stanu
Narzędzia do generacji kodu z bazy
Najpopularniejsze
Ruby on Rails
Castle ActiveRecord (.NET)
Problemy, kiedy model model biznesowy „rozjeżdża” się z
bazą
Active Record
10. Castle Activerecord - przykład
Post post = Post.Find(postId);
Comment comment = new Comment();
comment.Post = post;
comment.Author = Request.Form["Author"];
comment.DateAdded = DateTime.Now;
comment.Text = Request.Form["Comment"];
comment.Save();
[ActiveRecord("Comments")]
public class Comment : ActiveRecordBase<Comment>
{
[PrimaryKey]
public int Id { get; set; }
[BelongsTo("PostID")]
public Post Post { get; set; }
[Property]
public string Text { get; set; }
[Property]
public string Author { get; set; }
[Property]
public DateTime DateAdded { get; set; }
}
11. Całkowite odzwierciedlenie modelu biznesowego w obiektach
Nie koniecznie 1:1 z bazą
Nie tylko pojemniki na dane
Także logika – np. walidacja, relacje między obiektami
Obiekty nie wiedzą jak się zapisać
POCO (Plan Old CLR Object) i PI (Persistence Ignorance)
Domain Service
Kiedy akcja nie może być wewnątrz samego obiektu
Np. akcje transferu między dwoma kontami (korzysta z IRepository – same
encje nie powinny korzystać bezpośrednio z IRepository)
Proste do unit testów!
Domain Model
12. ViewMapper – generuje ViewModele na podstawie modelu
Messaging
Klasa bazowa + odpowiednie Request i Response
Application Service (frontend)
Koordynuje wszystkie aktywności aplikacji
Deleguje zadania do modelu domenowego
Może korzystać z repozytorium, kiedy trzeba (np. nowe konto)
Odbiera wiadomości – wykonuje akcje – odpowiada wiadomością
Transformuje encje modelu na DTO dla warstwy prezentacji
Domain Model – c.d.
13. Antywzorzec
Podobne do Domain Model
Wszelkie akcje nie wewnątrz obiektów, ale poza modelem
Tak naprawdę DTO
Domain Services w stylu Transaction Script
Naruszenie zasady „Tell, don’t ask”
Anemic Domain Model
14. Metodyka bazująca na wzorcu Domain Model
Wspólny język deweloperów, ekspertów dziedzinowych
Usprawnia komunikację między wykonawcami i biznesem
Ułatwia deweloperom zrozumienie reguł biznesowych
Encje
Unikalne, mają identyfikator (np. pesel lub inkrementowany int)
Value Objects
Nie mają ID
Nie żyją same z siebie (np. Transaction – żyje tylko w powiązaniu z Bank Account)
Aggregate
Logicznie pogrupowane obiekty pod względem celu modyfikacji danych
Agreggate Root
Jedyne encje, do których jest dostęp z zewnątrz agregatu
Np. dla ecommerce – Order (bo OrderLine czy aplikacja Voucheru zawsze przez Order – walidacja,
itp..)
Domain Driven Design
15. Domain Services
Metody nie pasujące do encji lub wymagające dostępu do repozytorium
Mogą także zawierać logikę biznesową – jest to część modelu domenowego
Application Services
Cienka warstwa pomiędzy modelem i aplikacją
Brak logiki biznesowej
Nie przechowuje stanu encji
Może przechowywać stan workflowu
Repository
Odpowiada za zapis danych
Podział na warstwy
DDD – c.d.
17. Creational pattern (GoF)
Ukrywanie skomplikowanego tworzenia obiektów
Z reguły klient nie prosi o konkretną klasę
Oczekujemy klasy abstrakcyjnej lub interfejsu
Factory decyduje jaka konkretna implementacja ma być zwrócona
Factory
18. Factory – c.d.
np. Factory zwracające obiekt generujący etykiety adresowe
19. Nowe cechy dodawane przez kompozycję
Unikamy tworzenia ogromnej liczby dziedziczących klas
Np. dodanie zniżki lub przelicznika waluty, logowanie, strumienie
Decorator
20. Struktura metody zdefiniowana
Cześć metod do zdefiniowania przez dziedziczące klasy
Template method
21. Zmiana zachowania przy zmianie stanu wewnętrznego
Podmiana wewnętrznych obiektów odpowiadających za
zachowania
State
23. Nic nie robi
Nie chcemy nie wyrzucać wyjątku, ale zgodne z interfejsem
Np. brak strategii
Null Object
public class NoBasketDiscount : IBasketDiscountStrategy
{
public decimal GetTotalCostAfterApplyingDiscountTo(Basket basket)
{
return basket.TotalCost;
}
}
25. Kompozyt - przykład
//Component
public interface IGraphic
{
void Print();
}
//Leaf
public class Ellipse : IGraphic
{
//Prints the graphic
public void Print()
{
Console.WriteLine("Ellipse");
}
}
public class CompositeGraphic : IGraphic
{
//Collection of Graphics.
private readonly List<IGraphic> graphics;
//Constructor
public CompositeGraphic()
{
//initialize generic Colleciton(Composition)
graphics = new List<IGraphic>();
}
//Adds the graphic to the composition
public void Add(IGraphic graphic)
{
graphics.Add(graphic);
}
//Adds multiple graphics to the composition
public void AddRange(params IGraphic[] graphic)
{
graphics.AddRange(graphic);
}
//Removes the graphic from the composition
public void Delete(IGraphic graphic)
{
graphics.Remove(graphic);
}
//Prints the graphic.
public void Print()
{
foreach (var childGraphic in graphics)
{
childGraphic.Print();
}
}
}
26. Specyfikacja – zapisanie prostej reguły (IsSatisfiedBy())
+ np. kompozyt – zagregowanie wielu mniejszych specyfikacji
Kompozyt i specyfikacja
27. Iterator
HasNext, GetNext
Visitor
Kiedy potrzebujemy wykonać akcję na danych encjach skomplikowanej
struktury (np. agent, który chodzi po drzewie)
Visitor, Iterator
28. W C# w zasadzie gotowe – eventy, delegaty
Lubi powodować wycieki pamięci
Strong reference
Weak Reference
Observer
29. Lock, podwójna weryfikacja, static, IoC
BeforeFieldInit – inicjalizacja bardziej lazy (na początku metody,
która może jej potrzebować)
W praktyce – na poniższym przykładzie zawsze
Bez BeforeFieldInit – może być zainicjalizowana przed użyciem
Sposób: statyczny konstruktor
Singleton
public static void DoSomething(bool which)
{
if (which)
{
FirstType.Foo();
}
else
{
SecondType.Bar();
}
}
36. Wstrzykiwanie zależności przez kontener IoC
StructureMap, Castle Windsor, Spring.NET, Ninject,
PicoContainer.NET, Unity, MEF, …
Właściwość, konstruktor
Service Locator
Bardziej luźno powiązane Factory*
Dependency Injection
OrderService orderService = serviceLocator.Locate<OrderService>(“OrderServiceWithFedExCourier”);
37. Kluczowe funkcjonalności te same
Różne platformy wspierane
Różnice przy bardziej zaawansowanych scenariuszach
Przykład - rozszerzenia – np. nasz kod w trakcie wstrzykiwania, itp.
Porównanie wydajności
Najszybszy – Simple Injector
DI – który wybrać?
38. Layer Super Type
Unikanie duplikacji
Np. EntityBase<T>, walidacja
Interface Segregation
Dzielimy duży interfejs na kilka mniejszych, pogrupowanych
logicznie
zamiast jednego dużego i NotImplementedException
Liskov Substitution Principle
Działanie klasy dziedziczącej powiąż z założeniami kontraktu klasy
bazowej
Inne wzorce enterprise
40. Wyraźne granice
Interfejs jak najbardziej czytelny jak możliwe, ujednolicone typy
komunikatów
Usługi są autonomiczne
Metody bezstanowe, zmiany atomic, nie konieczna określona
kolejność wywołań
Współdzielenie kontraktu i schema, nie klas
Kompatybilność określają polityki (np. WS-Policy)
Najważniejsze zasady SOA
41. Fasada - uproszczenie skomplikowanego API
Adapter – przystosowanie interfejsu jednej klasy do drugiej
Fasada i adapter
43. Długotrwały proces, wymagający
przechowywania stanu i wielu
komunikatów
Unikalny identyfikator po
pierwszym wywołaniu
Przekazywany przy kolejnych
Czas ważności
Wzorzec Reservation
44. Operacja indempotentna to taka, która może być wywołana wielokrotnie z tymi
samymi parametrami wejściowymi (nie wywołując błędu)
Wszystkie żądania modyfikujące stan powinny zawierać unikalny identyfikator
Identyfikator sprawdzany przed przetworzeniem komunikatu
Jeśli już był przetworzony – zwracana odpowiedź z response store dla danego ID
Identyfikator może być zwrócony w odpowiedzi (tzw. correlation ID)
Wzorzec Indempotent
46. Reprezentuje kolekcję obiektów
Mogą mieć zależności z innymi repozytoriami
Całkowicie wyizolowane od modelu domenowego
Interfejs separujący od konkretnej implementacji
IQueryable nie zalecane
Osobne dla każdego Aggregate Root
Repository
public interface IRepository<T>
{
IEnumerable<T> FindAll();
IEnumerable<T> FindAll(int index, int count);
IEnumerable<T> FindBy(Query query);
IEnumerable<T> FindBy(Query query, int index, int count);
T FindBy(Guid Id);
void Add(T entity);
void Save(T entity);
void Remove(T entity);
}
47. Podobna koncepcja do Repository, ale na niższym poziomie
Nie ukrywa, że reprezentuje tabelę z bazy
Z reguły jedno DAO dla jednej tabeli
Wykorzystywane często przy Active Record i Transaction Script
Data Access Objects
public interface IProductDAO
{
Product Get(int id);
IEnumerable<Product> FindByCategory(int id);
IEnumerable<Product> FindByBrand(int id);
IEnumerable<Product> FindByTopSelling(int count);
void Add(Product product);
void Save(Product product);
void Remove(Product product);
}
48. Rejestruje zmiany w obiektach
biznesowych
Dodanie, usunięcie, zmiana
Koordynuje zapis zmian w bazie
Transakcja, konflikty, itp.
Zapewnia integralność danych
RegisterAmended – przekazywana
encja
i repozytorium
Commit wywołuje odpowiednie metody
(np. PersistUpdateOf(acc)) na
odpowiednich repozyzoriach
Unit of Work
49. IAggregateRoot – pusty interfejs
Wzorzec Marker
Aplikowane do encji będących AggregateRoot
IUnitOfWorkRepository
IUnitOfWork
Wstrzykiwane do repozytorium – wiele repozytoriów może uczestniczyć w transakcji
Główne składniki Unit Of Work
public interface IUnitOfWorkRepository
{
void PersistCreationOf(IAggregateRoot entity);
void PersistUpdateOf(IAggregateRoot entity);
void PersistDeletionOf(IAggregateRoot entity);
}
public interface IUnitOfWork
{
void RegisterAmended(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void RegisterNew(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void RegisterRemoved(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository);
void Commit();
}
50. IDbSet – implementacja Repository
Mimo wszystko chcemy większej abstrakcji
DbContext – w zasadzie implementacja UoW
Przydaje się do współdzielenia kontekstu między repozytoriami
UoW i Entity Framework
52. Upewnia się, że każdy obiekt został załadowany tylko raz
Dostarcza tą samą wersję obiektów biznesowych transakcji
Przy dwukrotnym odwoływaniu się do obiektu, ta sama instancja
jest zwracana
Z reguły stosowane w ramach jednej transakcji
Identity Map
53. Reprezentuje zapytanie w języku domenowym
Unikamy pisania kodu dla każdego zestawu parametrów, np.:
Pozwala odseparować zapytanie od bazy danych
QueryTranslator
Tłumaczy zapytanie na język bazy
Analogiczne do providerów LINQ
Lepiej działa z procedurami składowanymi
Query Object
public interface ICustomerRepository
{
IEnumerable<Customer> FindAll();
IEnumerable<Customer> FindAllVIPCustomers();
IEnumerable<Customer> FindByOrder(Guid ID);
IEnumerable<Customer> FindAllCustomersThatHaveOutstandingOrders();
}
55. Najpopularniejszy wzorzec dla Web Forms / Windows Forms
Samodzielnie lub ew. frameworki (np. http://webformsmvp.com)
Model
Model biznesowy, modyfikowany lub wyświetlany przez widok
View
Wyświetla dane modelu pobierane przez Presenter
Deleguje akcje użytkownika do Presentera
Interfejs przekazywany do presentera (część private get / set)
Presenter
Wywoływany przez widok, w celu reakcji na akcje użytkownika
Może korzystać ze wstrzykiwanych zależności przez IoC
Daje się testować, ma wyodrębniony interfejs, brak zależności z httpcontext
Model-View-Presenter (MVP)
60. Kontroler pierwszy ma kontrolę, wykrywa który widok wyświetlić
Front Controller
Pasywny model, aktywny model
Page Controller – każda strona ma swój kontroler (code behind w ASP.NET)
Generalnie najlepiej ASP.NET MVC, ale da się samemu (HttpHandlery)
Łatwiejszy w utrzymaniu, ale więcej pracy
Model-View-Controller (MVC) i Front Controller
62. Chain of Responsibility
Łańcuch obiektów typu opakowujących akcję
Po kolei – wykonanie akcji lub przekazanie dalej
np. filtry poczty, routing
63. Specjalna klasa przesyłana przez AppService do widoku
(DTO)
Agregacja lub podzbiór danych modelu, potrzebne do
wyświetlenia widoku
Mapowanie ręczne lub frameworki
AutoMapper (http://automapper.codeplex.com)
ViewModel
Mapper.CreateMap<Order, OrderDto>();
OrderDto dto = Mapper.Map<Order, OrderDto>(order);