fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
4 minut

O mikro-kontraktach


03.02.2014

Praktycznie każdy jako-tako przemyślany system z jaką-taką architekturą zawiera “klasy z logiką”. Przez “jako-tako przemyślaną architekturę” rozumiem fakt, że cała logika nie siedzi bezpośrednio w kontrolerach czy innym tego typu bycie, a w dedykowanych… “miejscach”.

W naszym projekcie jednym z ważniejszych konceptów domenowych jest numer telefonu. Można go zareprezentować na przykład w taki sposób:

public class PhoneNumber
{
    public int Id { get; set; }
    public int OwnerId { get; set; }
    public string Number { get; set; }
    public string OriginalNumber { get; set; }
    public bool IsDisabled { get; set; }
    public bool IsDeleted { get; set; }
    public DateTime? LastDialingTime { get; set; }

    // more data and methods...

Podczas działania systemu niekiedy trzeba znaleźć duplikaty numerów na podstawie wartości, czyli “Number”. W projekcie, który ssie, byłoby to zaszyte w jakimś kontrolerze. U nas, jako że ssących projektów nie budujemy:), logika wyszukiwania duplikatów jest zaimplementowana w dedykowanym komponencie. Nazwijmy go IFindDuplicatePhoneNumbers.

Standardowym i przychodzącym właściwie momentalnie do głowy API takiego komponentu jest coś takiego:

public interface IFindDuplicatePhoneNumbers
{
    IDictionary<PhoneNumber, PhoneNumber[]> FindDuplicates(IEnumerable<PhoneNumber> numbers);
}

Jednak to jest rozwiązanie moim zdaniem nienajlepsze. Po pierwsze: do znalezienia duplikatów nie potrzebujemy informacji o czasie ostatniego połączenia ani całej masy innych danych dostępnych w klasie PhoneNumber. Po drugie: zmiana w klasie PhoneNumber mogłaby wpłynąć na działanie naszego “odszukiwacza duplikatów”. Po trzecie wreszcie: zarówno wykorzystanie tego API w kodzie jak i w testach mogłoby prowadzić do “skracania” sobie drogi i przekazywania obiektów niepoprawnych, na przykład mających wypełnione tylko pola Id i Number.

Jak można temu zaradzić? A rozszerzyć ten komponent o dodatkowe klasy dokładnie określające kontrakt wymagany do wykonania żądanej operacji, czyli struktury reprezentujące “input” i “output”:

public interface IFindDuplicatePhoneNumbers
{
    DuplicationFindingResult FindDuplicates(IEnumerable<DuplicateCandidate> numbers);
}

public class DuplicateCandidate
{
    public int Id { get; set; }
    public string Number { get; set; }
}

public class DuplicatedNumber
{
    public int Id { get; set; }
    public string Number { get; set; }

    public int[] DuplicateIds { get; set; }
}

public class DuplicationFindingResult
{
    public DuplicatedNumber[] Duplicates { get; set; }

    public bool DuplicatesFound
    {
        get { return Duplicates != null && Duplicates.Length > 0; }
    }
}

Czy coś w ten deseń. Pełna dowolność w zależności od przedstawionych wymagań.

Co nam to daje?

Podczas implementacji tej funkcjonalności nie zastanawiamy się jakie obiekty mamy do dyspozycji. Po prostu definiujemy te obiekty, a klient (czyli kod wywołujący tworzoną logikę) ma za zadanie odpowiednie dane dostarczyć. Możemy tutaj dowolnie napawać się swobodą stosując TDD w czystej postaci, gdzie nie ogranicza nas dotychczasowy kształt systemu – robimy co mamy do zrobienia i już. Zalet jest więcej. Odrywamy się od klasy będącej bezpośrednim mapowaniem na bazę danych. Tworzymy niezależny kawałek softu, który możemy nawet wystawić jako osobny serwis. Mamy wolną rękę jeśli chodzi o pisany kod.

Oczywiście, powoduje to mnożenie się klas w projekcie. Ale czy to źle? Nie.

Dodatkowo można zamiast klasy DuplicateCandidate zrobić sam interfejs IDuplicateCandidate i zaimplementować go w naszym “głównym” PhoneNumber, ale… ja osobiście wolę klasę. Między innymi dlatego, że mogę do niej dodać metody charakterystyczne dla tego scenariusza.

Od dłuższego czasu stosuję takie rozwiązanie w praktyce i sprawdza się to znakomicie. Zachęcam do spróbowania i oderwania się do reprezentowania każdej składowej systemu w jeden-li tylko sposób. Reprezentujmy dane tak, jak wymaga tego aktualny, konkretny, najwęższy możliwy kontekst. Prawdopodobnie ciąg dalszy nastąpi…

0 0 votes
Article Rating
12 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
trackback
10 years ago

O mikro-kontraktach | Maciej Aniserowicz o programowaniu…

Dziękujemy za dodanie artykułu – Trackback z dotnetomaniak.pl…

dario
dario
10 years ago

A ja myślałem, że będzie o mikro-kontraktach z klientami, a tu niespodzianka. :)
Wracając do tematu. Stosuję takie rozwiązanie od dłuższego czasu i potwierdzam, że dobrze się sprawdza. :)

Mic
Mic
10 years ago

Drugą stroną medalu jest niestety utrzymywanie jakiegoś tam mapowania między tymi obiektami, akurat tutaj wiele tego nie ma.
No, ale chyba lepszej opcji nie ma,

@rek
10 years ago

powoli zaczynam używać czegoś takiego w controllerach w mvc

https://github.com/abenedykt/appEngine

controller praktycznie wygląda wtedy app.Execute(request) a projektując request (w tym przypadku) samo się narzuca RequestDuplicatesFor i tutaj nr tel czy ewentualnie to id

Alex
Alex
10 years ago
Reply to  procent

No tutaj akurat nie. Natomiast jak pewno zdążyłeś zauważyć, obiekty domenowe często nie są potrzebne w calości do rozwiązania danego problemu ( tak jak tu dałeś w swoim przykładzie ). Teraz pytanie brzmi , czy wolimy implementować rozwiązanie dla tego typu danych ( w Twoim wypadku dla PhoneNumber ) czy dla dedykowanego problemowi input type ( tak jak to zrobiłeś ). Wszystko , jak zwykle, zależy od tego, czy potencjalnie simplifikacja input type się może opłacić ( być może w wypadku specyfikacji Twojego projektu się to oplaca – w końcu sam numer to dużo mniej niż wiele innych danych w Twoim modelu domenowym ) w przyszłości :D

Dobry artykuł overall, będe obserwowal ten blog. ^^

@rek
10 years ago
Reply to  procent

podeślij gista, może coś ciekawego wyczytam. Generalnie budowanie może być zrzuconen na autofaca czy cokolwiek, generlanie mi chodzi o samo mapowanie requesta na handler-a. Co do wrzucania api do handlera to chyba jakaś inna koncepcja :)

Gutek
10 years ago

true, stosuje od kilku lat takie rozwiazanie i jestem z niego bardzo zadowolony :)

Marcin
Marcin
10 years ago

Dal mnie to trochę jak wykorzystywanie koparki wprost z kamieniołomu do przekopania ogródka 2mx3m przed blokiem. Przynajmniej dla takich przypadków jak ten zaprezentowany. Przecie te 3 klasy kontraktu i 1 interfejs wraz z jego implementacją opakowywują najzwyklejszego w świecie GroupBy’ja.

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również