dotnet-architecture / eShopOnContainers

Cross-platform .NET sample microservices and container based application that runs on Linux Windows and macOS. Powered by .NET 7, Docker Containers and Azure Kubernetes Services. Supports Visual Studio, VS for Mac and CLI based environments with Docker CLI, dotnet CLI, VS Code or any other code editor. Moved to https://github.com/dotnet/eShop.
https://dot.net/architecture
24.55k stars 10.36k forks source link

Why the UnitofWork is present in the Repository #1869

Closed MuraliM closed 2 years ago

MuraliM commented 2 years ago

I see the unit of work is a logical container that works along with multiple repositories. Even I see the article uses same https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

But according to your implementation it looks below

namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;

public interface IRepository<T> where T : IAggregateRoot
{
    IUnitOfWork UnitOfWork { get; }
}

Then we call like

await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
await _buyerRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

I see this looks different than other way where we put all the repositories in the UnitOfWork like below

public interface IUnitOfWork
{
    IOrderRepository OrderRepository { get; }
    IBuyerRepository OrderRepository { get; }
    Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken));
}

then make a single call to the UnitOfWork like below which hides the different repositories it is dealing with await _unitOfWork.SaveEntitiesAsync();

This way also helps us to inject just a UnitOfWork in the constructor instead of N repositories.

Could you please explain the reason of design the UnitOfWork in Repository and the benifits of this approach compared to keeping the repositories inside UnitOfWork. Thanks in advance!

nhdinh commented 2 years ago

In the view of microservice, there is only one aggregate in each service. And that aggregate should maintain the state of all entities in the service. Repository is responsible for getting Aggregate out of persistence and return to Service layer. In this way, uow will be the helper for repository to maintain its session working with persistence database. One service, one repository and one aggregate in a single service. Therefore, I see no reason why to put many repositories in a single unitofwork.

MuraliM commented 2 years ago

@nhdinh Do we need a UnitOfWork for a single repository in this case?

nhdinh commented 2 years ago

@MuraliM Yes, since the mentioned single repository is for accessing the data of Aggregate. But aggregate contains and has relationship with many of entities and data along it. Since then, the UnitOfWork is still need to maintain the session commit.

erjain commented 2 years ago

Hi @nhdinh, Thanks for the clarification on the UnitofWork. @MuraliM, we are closing this issue. Please feel free to open the conversation in case you need more clarification.

MithrilMan commented 2 years ago

@erjain I don't agree with the given explainations and I don't think this should be closed.

A service doesn't need to have only one aggregate, maybe @nhdinh wanted to say "aggregate root" but again a service can have multiple. Even in this repository, the Order service has 2 aggregate roots: Order and Buyer.

Aggregate root repositories are sharing the same unit of work. Given the fact that entity framework is used, if you alter two different aggregate roots in an operation (command in CQRS terminology) directly or by a side effect and you call aggregate_A_Repository.UnitOfWork.SaveEntitiesAsync, under the hood even the modifications done on the aggregateB are persisted.

Ending line of my comment is: actually current repository/unitofwork is quite confusing, also the Update method doesn't have sense because even without calling it, your entity will still be persisted - with all changes done - after the command ends, because of how the TransactionBehavior works

I'd like to have feedback on this about the persons who made this repository (and MS documentation/ebook) because I've a feeling like there is a lack of cohesion between the good intention (a reference for microservices + ddd + ... ) with current implementation, are tye still hanging around?

MithrilMan commented 2 years ago

About the "auto-save" that's happening under the hood in any CQRS commands, it's because of how CommitTransactionAsync has been implemented in the dbcontext, see here

https://github.com/dotnet-architecture/eShopOnContainers/blob/7b7f65e755469b550bbfec10d864b30932bb5527/src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs#L72-L76

Everytime a command ends, it automatically calls SaveChangesAsync before committing the transaction.