solidservices icon indicating copy to clipboard operation
solidservices copied to clipboard

How to reuse get-by-id queries?

Open Rodrickk opened this issue 8 years ago • 5 comments

hey Steven,

I'm currently undergoing my refactoring on the company framework so it uses CQRS. Like I said we are using entity framework as our O/RM. A few things questions that I don't know how to solve has arisen:

  1. I'm not really keen on having loads of QueryById types of queries. Is there a way we can have a QueryById<Entity> to avoid those?

  2. On the writing side, when using entity framework, we typically get the object from the database first and then update the object. But if our repositories for the writing side only have update, delete and create available, how can we do this? not only that but there can be situations where we need to make a query (or queries) before updating for whatever reasons. How do you handles these on CQRS?

thanks again for being so active and helping everyone out :)

Rodrickk avatar Nov 01 '17 00:11 Rodrickk

I'm not really keen on having loads of QueryById types of queries. Is there a way we can have a QueryById<Entity> to avoid those?

Sure that's possible. Here's an example of a generic query/handler pair:

public class GetById<TEntity> : IQuery<TEntity> where TEntity : class
{
    public Guid Id;
}

public class GetByIdHandler<TEntity> : IQueryHandler<GetById<TEntity>, TEntity>
    where TEntity : class
{
    private readonly MyDbContext context;
    public GetByIdHandler(MyDbContext context) => this.context = context;

    public TEntity Handle(GetById<TEntity> query) => 
        this.context.Set<TEntity>().Find(query.Id)
        ?? throw new KeyNotFoundException($"No {typeof(TEntity).Name} with id {query.Id}.");
}

Do note though that you will have to decide whether or not it is okay for your application to expose domain entities to other layers. In most applications I write, I tend to hide them by exposing more tailored DTOs to other layers.

Exposing entities on the other hand typically makes more sense when your application is very CRUDy in nature. This is something I tend to prevent, because there is often a lot of benefit in exposing Task Based interfaces. Even with queries, you typically don't need all columns or want to add aggregated information in the list, even if it just shows a list of customers.

So instead of exposing a Customer, I might expose a CustomerDto of some sort, but that does complicate mapping a bit, and might prevent you from being able to define generic query handlers. You might still be able to define a generic query though:

public class GetCustomerByIdHandler : IQueryHandler<GetById<CustomerDto>, CustomerDto>
{
    private readonly MyDbContext context;
    public GetByIdHandler(MyDbContext context) => this.context = context;

    public TEntity Handle(GetById<CustomerDto> query) => Map(
        // GetById = custom extension method on DbSet that calls Find internally and throws if null.
        this.context.Set<Customer>().GetById(query.Id));
    
    private CustomerDto Map(Customer customer) => new CustomerDto { ... };
}

On the writing side, when using entity framework, we typically get the object from the database first and then update the object. But if our repositories for the writing side only have update, delete and create available, how can we do this? not only that but there can be situations where we need to make a query (or queries) before updating for whatever reasons. How do you handles these on CQRS?

I think Tuukka Haapaniemi pretty much summed this up. You can use queries from within commands (but not the other way around)

But if our repositories for the writing side only have update, delete and create available, how can we do this?

I'm not sure why your repositories wouldn't have a read operation to get the single entity. Seems like quite basic operation for a command. To me there seems no reason to abstract away such basic read operation behind a query handler. To me that would only complicate things. Also remember the last paragraphs in my article: although everything in theory is either a command or a query, you shouldn't over-use those abstractions.

dotnetjunkie avatar Nov 01 '17 08:11 dotnetjunkie

not only that but there can be situations where we need to make a query (or queries) before updating for whatever reasons. How do you handles these on CQRS?

There are many reasons for this. for instance:

  • When the client is in a multi-step ordering process, when he hits the "Order" button, you might want do a last-minute check for the availability of the products, before processing the "OrderGoods" command. This gives you the ability to have user-friendly error messages explaining that he just mist the last item in stock.
  • When there are items in stock, we can create the command, but even than, before we execute its command handler, we might want to do some validation. This validation might require querying the database. This is not part of the command handler, but might still be part of the same transaction, or same DbContext. Or you might explicitly decide to isolate validation and execution from one another.

These are just two reasons I can think of from the top of my head, but there are many more examples imaginable.

dotnetjunkie avatar Nov 01 '17 08:11 dotnetjunkie

Hey steven. Thank you for your help ^^

Do note though that you will have to decide whether or not it is okay for your application to expose domain entities to other layers. In most applications I write, I tend to hide them by exposing more tailored DTOs to other layers.

I never expose my entities and always have some DTO (and usually use an automapper to handle transformation).

I'm not sure why your repositories wouldn't have a read operation to get the single entity. Seems like quite basic operation for a command. To me there seems no reason to abstract away such basic read operation behind a query handler. To me that would only complicate things. Also remember the last paragraphs in my article: although everything in theory is either a command or a query, you shouldn't over-use those abstractions.

Well I was thinking in having 2 repository projects for create/update/create that would only expose these and then another repository project with only the reads. That's why I was trying to see how i would resolve queries in the command part. My reasoning for this division is to make sure developers wouldn't fall into temptation and in a query do CRUD operations.

wheeeels avatar Nov 02 '17 11:11 wheeeels

Well I was thinking in having 2 repository projects for create/update/create that would only expose these and then another repository project with only the reads. That's why I was trying to see how i would resolve queries in the command part. My reasoning for this division is to make sure developers wouldn't fall into temptation and in a query do CRUD operations.

Having different abstractions for the command side opposed to the query side can make sense, but no matter how you do it, you will always need to read stuff when executing a command. You at least need to load the current state from the database before being able to mutate it.

dotnetjunkie avatar Nov 02 '17 11:11 dotnetjunkie

yea that makes a lot of sense. I dont know why mind got stuck on this. After reading Tuukka answer i remembered homer simpson with "Doh" lool.

wheeeels avatar Nov 02 '17 11:11 wheeeels