Reputation: 709
I know similar questions have been discussed several times, but my problem is slightly different, I guess. I'm experimenting with application architecture based on Domain Driven Design, using repository pattern for data access and Entity Framework infrastructure.
What I'm trying to accomplish is to have unit testable system which would have awareness of unit of works as well.
I like a design where there are core services in the system which take care of all the business logic in the application, i.e. you have some sort of CustomerService.AddOrder(int customerId, Order order)
instead of ICustomerRepository.Find(int id).Orders.Add(Order order)
. You have a easier and intuitive interface to work with using this approach.
(Of course the CustomerService
is dependent on ICustomerRepository
and probably IOrderRepository
as well, but it'll take care of the logic itself).
But! here comes the unit of work problem with this approach:
I'd like to have controllable unit of works inside the core services, i.e. I need to be able to start a new unit of work, do the job and DISPOSE it.
One way of doing this that I came up with is:
public interface IUnitOfWork
{
ICustomerRepository CustomerRepository { get; set; }
IOrderRepository OrderRepository { get; set; }
}
public interface IUnitOfWorkFactory
{
void New(Action<IUnitOfWork> work); // this will let you create and then dispose a new instance of IUnitOfWork implementation
}
public class CustomerService
{
private IUnitOfWorkFactory _uow { get; private set; }
public CustomerService(IUnitOfWorkFactory uowFactory)
{
_uow = uowFactory;
}
public void AddNewOrder(int id, string newName)
{
_uow.New(work =>
{
var customer = work.CustomerRepository.Find(id);
// Do some other required stuff
work.Commit();
});
}
}
After that you just have to create implementations for IUnitOfWorkFactory
, IUnitOfWork
and repositories; in the client code you just have to depend on CustomerService
and that will be easily taken care of by IOC containers.
I like this approach, because it's kinda compact, well structured, logically organised and intuitive, but THE PROBLEM is that I don't know how to correctly UNIT test the services (e.g. behavioral testing). Integration tests are easy, but they are not my concern at this point.
Any ideas will be appreciated.
Many thanks!
Upvotes: 1
Views: 1436
Reputation: 41
Maybe you could create a UnitOfWorkScope class that manages your active UnitOfWorks:
private CustomerRepository customerRepository;
'...
using (UnitOfWorkScope scope = new UnitOfWorkScope())
{
customer = customerRepository.GetByID(id);
customer.BuySomething(price)
'...
scope.complete()
}
End Using
Upvotes: 1
Reputation: 2990
What you just described are a form of domain services. I tend not to expose my domain code to the UoW, but admittedly, that's a personal preference. Having domain services control UoW scope gives them a responsibility they shouldn't have (it's an application service at that point). Instead, I'd have the domain service depend on the repositories (and optionally other services) it needs to collaborate with (makes it explicit enough but not confusing). You seem to be introducing interfaces for the purpose of making it testable/pluggable, but you're not gaining much from them at this point. Might a better strategy not be to take a dependency on an inmemory dbcontext (EF)? I believe there's a nuget package for that. That way, you could inject the inmemory dbcontext in the repository (I'm assuming you'd want to keep these) implementations. The SUT factory (the thing that creates the system under test, i.e. something that creates a domain service in this case) could then wire everything together, allowing you to control and assert using the inmemory dbcontext. Alternatively you could create your own inmemory repositories, or use mocking (but that's gonna be brittle and a world of pain). As you write your first few tests, keep watching out for verbosity, and refactor it relentlessly. Not doing so is going to make those tests either cumbersome to write or a burden to maintain in the long haul.
Upvotes: 2