Upload
bartlomiej-zass
View
1.627
Download
6
Embed Size (px)
DESCRIPTION
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.
Citation preview
Wzorce projektowe w ASP.NETBartłomiej ZassMicrosoft
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
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
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
Typowa architektura aplikacji
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
Organizacja warstwy logiki biznesowej
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
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ą Tzw. Impedance Mismatch
Active Record
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; } }
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
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.
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
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
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.
Wzorce warstwy logiki biznesowej
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
Factory – c.d. np. Factory zwracające obiekt generujący etykiety
adresowe
Nowe cechy dodawane przez kompozycję Unikamy tworzenia ogromnej liczby dziedziczących klas Np. dodanie zniżki lub przelicznika waluty, logowanie,
strumienie
Decorator
Struktura metody zdefiniowana Cześć metod do zdefiniowania przez dziedziczące
klasy
Template method
Zmiana zachowania przy zmianie stanu wewnętrznego Podmiana wewnętrznych obiektów odpowiadających za
zachowania
State
Pozwala wybierać algorytm w czasie wykonywania
Strategy
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; } }
Pozwala kolekcje traktować tak jak pojedynczą instancję
Kompozyt
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(); } } }
Specyfikacja – zapisanie prostej reguły (IsSatisfiedBy()) + np. kompozyt – zagregowanie wielu mniejszych
specyfikacji
Kompozyt i specyfikacja
Iterator HasNext, GetNext
Visitor Kiedy potrzebujemy wykonać akcję na danych encjach
skomplikowanej struktury (np. agent, który chodzi po drzewie)
Visitor, Iterator
W C# w zasadzie gotowe – eventy, delegaty Lubi powodować wycieki pamięci
Strong reference Weak Reference
Observer
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(); } }
Brak inversion of control
Dependency inversion
Interface Inversion – źle!
Interface Inversion - lepiej
Skąd przychodzą zależności?
Konstruktor
Skąd przychodzą zależności?
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”);
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ć?
Layer Super Type Unikanie duplikacji Np. EntityBase<T>, walidacja
Interface Segregation Dzielimy duży interfejs na kilka mniejszych,
pogrupowanych logiczniezamiast jednego dużego i NotImplementedException
Liskov Substitution Principle Działanie klasy dziedziczącej powiąż z założeniami
kontraktu klasy bazowej
Inne wzorce enterprise
Warstwa usług
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
Fasada - uproszczenie skomplikowanego API Adapter – przystosowanie interfejsu jednej klasy do
drugiej
Fasada i adapter
RequestMessage, ResponseMessage, … Extension methods do konwersji modelu na
komunikat response
Document Message i Request-Response
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
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
Warstwa dostępu do danych
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); }
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); }
Rejestruje zmiany w obiektach biznesowych Dodanie, usunięcie, zmiana
Koordynuje zapis zmian w bazie Transakcja, konflikty, itp.
Zapewnia integralność danych
RegisterAmended – przekazywana encjai repozytorium
Commit wywołuje odpowiednie metody(np. PersistUpdateOf(acc)) na odpowiednich repozyzoriach Opakowane w transakcję
Unit of Work
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(); }
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
Lazy Loading Natywnie w .NET – IQueryable
Proxy Maskowanie długotrwałego wywoływania operacji Cache, bezpieczeństwo
synchronizacja, …
Proxy
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
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(); }
Warstwa prezentacji
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)
MVP – najpopularniejsze odmiany
MVP Passive View
MVP – diagram sekwencji
Hermetyzacja wykonania akcji Execute, CanExecute
Np. cofnij/ponów, makra, itp.
Command
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
Command i Front Controller
Chain of Responsibility Łańcuch obiektów typu opakowujących akcję Po kolei – wykonanie akcji lub przekazanie dalej
np. filtry poczty, routing
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);
© 2012 Microsoft Corporation. Wszelkie prawa zastrzeżone. Microsoft, Windows oraz inne nazwy produktów są lub mogą być znakami towarowymi lub zastrzeżonymi znakami towarowymi firmy Microsoft w Stanach Zjednoczonych i innych krajach. Zamieszczone informacje mają charakter wyłącznie informacyjny. FIRMA MICROSOFT NIE UDZIELA ŻADNYCH GWARANCJI (WYRAŻONYCH WPROST LUB DOMYŚLNIE), W TYM TAKŻE USTAWOWEJ RĘKOJMI ZA WADY FIZYCZNE I PRAWNE, CO DO INFORMACJI ZAWARTYCH W TEJ PREZENTACJI.