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…

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Notify of
trackback

O mikro-kontraktach | Maciej Aniserowicz o programowaniu…

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

dario
dario

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

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

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

Gutek

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

Marcin
Marcin

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.

Moja książka

Facebook

Zobacz również