A może by tak robić API na .NET Core z wykorzystaniem CQRS? Albo, jak robić mikroserwisy w podejściu DDD

No właśnie, taki popularny wzorzec architektoniczny a nikt nie chce go używać. Co prawda używają, ale na potrzeby tego wpisu załóżmy że nie 😉

Nie będę tutaj definiował, czym jest CQRS bo za mnie już to zrobił Pan Martin Fowler (https://martinfowler.com/bliki/CQRS.html), powiem tylko krótko – CQRS jest oparty o ideologię rozgraniczenia odpowiedzialności na odczyt i zapis, gdzie Queries mogą tylko odczytywać dane a zapisywać dane mogą tylko Commands. W swojej niezbyt długiej karierze widziałem kilka podejść do implementacji tego wzorca w projektach różnej wielkości, ale większość z tych projektów i tak korzystała z zdefiniowanych w Controller’ach akcjach, które wołały albo sam Query/Command albo jakiś Handler. IMO, w takim podejściu niepotrzebnie zwiększamy poziom abstrakcji w projekcie i w 99.9(9)% przypadków przytoczone wyżej Controller’y służą tylko jako przelotka do warstwy niżej.

Dlatego chcę przedstawić Państwu niedużą, bo liczy zaledwie kilka klas, bibliotekę o prostej nazwie ASPNET.CQRS, która umożliwi Państwu pozbycie się zbędnych Controller’ów i pisanie projektu w bardziej DDD podejściu. Co prawda, nie jest to jeszcze finalny release, ale już wykorzystuję ją w kilku mniejszych i jednym większym API.

Otóż, biblioteka definiuje dodatkowy middleware, który przejmuje kontrolę nad wykonaniem poszczególnych Queries i Commands a same Queries mogą być proste (simple, bez payloadu) i skomplikowane (complex, zawierające payload), natomiast same komendy są zawsze skomplikowane (complex) choć mogą nie zawierać żadnego payload’u. Dodatkowo w wersji v1.0.0-rc3 zostanie dodana możliwość definiowania komend fire & forget, gdzie zwrócenie odpowiedzi do consumera API odbywa się nie oczekując na wykonanie komendy.

Użycie samej biblioteki w projekcie jest proste i zgodnie z dawną zasadą Microsoft’a – plug & play. Zdefiniujmy przykładowe zapytanie, które zwróci consumer’owi naszego API przywitanie:

public class SayHelloQuery : IQuery
{
    public string Name { get; set; }
}

Oraz DTO dla wyniku wykonania zapytania i zwrócenia do klienta:

public class SayHelloQueryResult
{
    public string Message { get; set; }
}

Ostatni krok, to zdefiniowanie logiki biznesowej dla naszego zapytania, np. wykonania zapytania na bazie danych czy odwołanie do innego mikroserwisu. W naszym przykładowym przypadku, zwrócimy klientowi wiadomość powitającą:

[CQRSRoute("/say-hello")]
public class SayHelloQueryHandler : IQueryHandler<SayHelloQuery, SayHelloQueryResult>
{
   public Task<SayHelloQueryResult> Handle(SayHelloQuery parameters)
   {
       return Task.FromResult(new SayHelloQueryResult
       {
           Message = $"Hello, {parameters.Name}"
       });
    }
}

Żeby biblioteka mogła zarejestrować i prawidłowo wykonywać zapytania w modelu CQRS należy dodatkowo jednorazowo skonfigurować ją:

  1. Najpierw dodajemy referencje do własnego projektu Web API (aktualna wersja gotowa do użycia w Waszych projektach na moment publikacji tego wpisu to v1.0.0-rc2):
dotnet add package ASPNET.CQRS --version 1.0.0-rc2
  1. Teraz zkonfigurować endpointy oraz wskazać z którego Assembly należy ładować Wasze Queries i Commands:
services.AddCQRS(options => {
    options.BasePath = "/api";
    options.Assemblies = new[] { Assembly.GetExecutingAssembly() };
});
  1. I finalnie użyć nowego middleware do operowania na własnych Queries & Commands:
app.UseCQRS();

Wynik działania zapytania można sprawdzić wysyłając żądanie HTTP pod odpowiednio skonfigurowany route:

curl --request GET \
  --url 'http://localhost:5000/api/say-hello?name=Vladyslav%20Chyzhevskyi'
{
  "Message": "Hello, Vladyslav Chyzhevskyi"
}

Więcej o bibliotece i przykładów użycia można znaleźć na publicznym repozytorium https://github.com/vchyzhevskyi/aspnet.cqrs

Zapraszam do komentowania rozwiązanie, propozycji rozszerzenia funkcjonalności, PRów z ulepszeniami i zgłaszanie uwag i wykrytych błędów 😇