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ą:
dotnet add package ASPNET.CQRS --version 1.0.0-rc2
services.AddCQRS(options => {
options.BasePath = "/api";
options.Assemblies = new[] { Assembly.GetExecutingAssembly() };
});
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 😇