vkhorikov / cqrsinpractice Goto Github PK
View Code? Open in Web Editor NEWSource code for the CQRS in Practice Pluralsight course
Home Page: https://enterprisecraftsmanship.com/ps-cqrs
License: MIT License
Source code for the CQRS in Practice Pluralsight course
Home Page: https://enterprisecraftsmanship.com/ps-cqrs
License: MIT License
Much appreciated
I'm just watching your course and one thing bugs me.
In [Segregating Commands and Queries > Commands and Queries in the Onion Architecture], you said that Commands and Queries belong to the core domain layer. That makes sense, since they act as an interface to the core domain layer.
Then in [Implementing Decorators upon Command and Query Handlers > Command and Query Handlers Best Practices] you convert Handlers to nested classes inside Commands.
To my understanding, the handlers do not belong to the core logic. They rather belong to the application services (in your code they even exist in "AppServices" directory). Just looking at their dependencies reveals domain impurity (e.g. SessionFactory).
It feels like you merged the 2 extreme layers from the onion architecture into one.
Could you please explain why? Or maybe, am I missing something?
Hi
How should we test decorators?
For example for testing the DatabaseRetryDecorator, we should mock ICommandHandler to throws an exception. Right?
But based on your unit testing book and fragile tests, We can use mocks just for unmanaged dependencies.
how to test Decorator pattern?
Hi Vladimir.
How to test (integration test) a CommandHandler when it is internal ?
1)We can make it public (just for the matter of testing!!!!).
2)Or we can test controller's action methods. If we test controller's method then our tests cover the Messages
too (it is good), but on the other hand, we get involved in working with Json in tests and we know that output json format is changed frequently. Besides that Messages
uses an IServiceProvider and it is hard to create an instance of it in tests.
What do you suggest?
public sealed class EnrollCommand : ICommand
{
public long Id { get; }
public string Course { get; }
public string Grade { get; }
public EnrollCommand(long id, string course, string grade)
{
Id = id;
Course = course;
Grade = grade;
}
internal sealed class EnrollCommandHandler : ICommandHandler<EnrollCommand>
{
private readonly SessionFactory _sessionFactory;
public EnrollCommandHandler(SessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
public Result Handle(EnrollCommand command)
{
var unitOfWork = new UnitOfWork(_sessionFactory);
var courseRepository = new CourseRepository(unitOfWork);
var studentRepository = new StudentRepository(unitOfWork);
Student student = studentRepository.GetById(command.Id);
if (student == null)
return Result.Fail($"No student found with Id '{command.Id}'");
Course course = courseRepository.GetByName(command.Course);
if (course == null)
return Result.Fail($"Course is incorrect: '{command.Course}'");
bool success = Enum.TryParse(command.Grade, out Grade grade);
if (!success)
return Result.Fail($"Grade is incorrect: '{command.Grade}'");
student.Enroll(course, grade);
unitOfWork.Commit();
return Result.Ok();
}
}
}
public sealed class StudentController : BaseController
{
private readonly Messages _messages;
public StudentController(Messages messages)
{
_messages = messages;
}
[HttpPost("{id}/enrollments")]
public IActionResult Enroll(long id, [FromBody] StudentEnrollmentDto dto)
{
Result result = _messages.Dispatch(new EnrollCommand(id, dto.Course, dto.Grade));
return FromResult(result);
}
}
public sealed class Messages
{
private readonly IServiceProvider _provider;
public Messages(IServiceProvider provider)
{
_provider = provider;
}
public Result Dispatch(ICommand command)
{
Type type = typeof(ICommandHandler<>);
Type[] typeArgs = { command.GetType() };
Type handlerType = type.MakeGenericType(typeArgs);
dynamic handler = _provider.GetService(handlerType);
Result result = handler.Handle((dynamic)command);
return result;
}
public T Dispatch<T>(IQuery<T> query)
{
Type type = typeof(IQueryHandler<,>);
Type[] typeArgs = { query.GetType(), typeof(T) };
Type handlerType = type.MakeGenericType(typeArgs);
dynamic handler = _provider.GetService(handlerType);
T result = handler.Handle((dynamic)query);
return result;
}
}
Hi,
I just saw your course, great job.
As you mentioned in The Read Model and the Onion Architecture
, the DTOs belongs to outer later of Onion Architecture, the UI layer, but Microsoft believes DTOs belongs to core layer. so which one is correct and why?
Thank you!
Hey,
Your course on this was really great!
In the introduction of the course you mention that you will introduce the open source library MediatR. Later on you implement your own 'mediator'.
Is there any reason why you prefer your own implementation over MediatR?
Thanks in advance.
Hi @vkhorikov! I'm a big fan of your PS courses abd blogposts. I often use the course repos as reference when designing my API. Great job, keep doint it!
There is one things that bothers me in this particular repo. Why does AuditLoggingDecorator
(or DatabaseRetryDecorator
) expose its structure and implementatin details in the class name? The purpose of the first class is to introduce logging behavior. That's it. Looking on the class name, why should I care if it's a decorator or not? Nothing should prevent me from refactoring the class and replacing decorator with, let's say, inheritense from some base class or even copy-pasting from other source (!). Of course, composition is preferable, but why should it go to the class name? Will I have to rename it to AuditLoggingInheritedFromBase
or AuditLoggingCopyPaste
in case of not using Decorator pattern? Sounds awkward, isn't it?
Wouldn't it be better to have a class called AuditLoggingCommandHandler
? This name provides clear understanding of the class purpose and is resistant to inner implementation changes. Any thoughts regarding this?
Hi Vladimir.
This isn't a technical issue with this repository so much as it is a question for you.
Using a CQRS approach like this repository does, do you have any recommendations on how one would go about responding with different http status codes depending on the nature of a command's failure without API (UI) logic leaking into the application services layer?
Example:
In your StudentController (https://github.com/vkhorikov/CqrsInPractice/blob/master/After/src/Api/Controllers/StudentController.cs) the following method takes in a Student's ID and a StudentEnrollmentDto:
[HttpPost("{id}/enrollments")]
public IActionResult Enroll(long id, [FromBody] StudentEnrollmentDto dto)
{
Result result = _messages.Dispatch(new EnrollCommand(id, dto.Course, dto.Grade));
return FromResult(result);
}
What might we do if we wanted to respond with a 404 error if no matching Student is found with the provided ID, but respond with a generic 400 if some other expected failure occurs in the EnrollCommand handler?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.