Question

I have a design problem with my poject that I don't know how to fix, I have a DAL Layer which holds Repositories and a Service Layer which holds "Processors". The role of processors is to provide access to DAL data and perform some validation and formatting logic.

My domain objects all have a reference to at least one object from the Service Layer (to retrieve the values of their properties from the repositories). However I face two cyclical dependencies. The first "cyclical dependency" comes from my design since I want my DAL to return domain objects - I mean that it is conceptual - and the second comes from my code.

  1. A domain object is always dependent of at least one Service Object
  2. The domain object retrieves his properties from the repositories by calling methods on the service
  3. The methods of the service call the DAL
  4. However - and there is the problem - when the DAL has finished his job, he has to return domain objects. But to create these objects he has to inject the required Service Object dependencies (As these dependencies are required by domain objects).
  5. Therefore, my DAL Repositories have dependencies on Service Object.

And this results in a very clear cyclical dependency. I am confused about how I should handle this situation. Lastly I was thinking about letting my DAL return DTOs but it doesn't seem to be compatible with the onion architecture. Because the DTOs are defined in the Infrastructure, but the Core and the Service Layer should not know about Infrastucture.

enter image description here

Also, I'm not excited about changing the return types of all the methods of my repositories since I have hundreds of lines of code...

I would appreciate any kind of help, thanks !

UPDATE

Here is my code to make the situation more clear :

My Object (In the Core):

public class MyComplexClass1
{
    MyComplexClass1 Property1 {get; set;}
    MyComplexClass2 Property2 {get; set;}
    private readonly IService MyService {get; set;}

    public MyComplexClass1(IService MyService)
    {
       this.MyService = MyService;
       this.Property1 = MyService.GetMyComplexClassList1();
       .....
    }

This is my Service Interface (In the Core)

 public interface IService
 {
     MyComplexClass1 GetMyComplexClassList1();
     ...
 }

This my Repository Interface (In the Core)

public interface IRepoComplexClass1
{
     MyComplexClass1 GetMyComplexClassObject()
     ...
}

Now the Service Layer implements IService, and the DAL Layer Implements IRepoComplexClass1.

But my point is that in my repo, I need to construct my Domain Object

This is the Infrascruture Layer

using Core;

public Repo : IRepoComplexClass1
{
   MyComplexClass1 GetMyComplexClassList1()
   {
       //Retrieve all the stuff...
       //... And now it's time to convert the DTOs to Domain Objects
       //I need to write 

       //DomainObject.Property1 = new MyComplexClass1(ID, Service);
       //So my Repository has a dependency with my service and my service has a dependency with my repository, (Because my Service Methods, make use of the Repository). Then, Ninject is completely messed up.
   }

I hope it's clearer now.

Was it helpful?

Solution

First of all, typically architectural guidance like the Onion Architecture and Domain Driven Design (DDD) do not fit all cases when designing a system. In fact, using these techniques is discouraged unless the domain has significant complexity to warrant the cost. So, the domain you are modelling is complex enough that it will not fit into a more simple pattern.

IMHO, both the Onion Architecture and DDD try to achieve the same thing. Namely, the ability to have a programmable (and perhaps easily portable) domain for complex logic that is devoid of all other concerns. That is why in Onion, for example, application, infrastructure, configuration and persistence concerns are at the edges.

So, in summary, the domain is just code. It can then utilize those cool design patterns to solve the complex problems at hand without worrying about anything else.

I really like the Onion articles because the picture of concentric barriers is different to the idea of a layered architecture.

In a layered architecture, it is easy to think vertically, up and down, through the layers. For example, you have a service on top which speaks the outside world (through DTOs or ViewModels), then the service calls the business logic, finally, the business logic calls down to some persistence layer to keep the state of the system.

However, the Onion Architecture describes a different way to think about it. You may still have a service at the top, but this is an application service. For example, a Controller in ASP.NET MVC knows about HTTP, application configuration settings and security sessions. But the job of the controller isn't just to defer work to lower (smarter) layers. The job is to as quickly as possible map from the application side to the domain side. So simply speaking, the Controller calls into the domain asking for a piece of complex logic to be executed, gets the result back, and then persists. The Controller is the glue that is holding things together (not the domain).

So, the domain is the centre of the business domain. And nothing else.

This is why some complain about ORM tools that need attributes on the domain entities. We want our domain completely clean of all concerns other than the problem at hand. So, plain old objects.

So, the domain does not speak directly to application services or repositories. In fact, nothing that the domain calls speaks to these things. The domain is the core, and therefore, the end of the execution stack.

So, for a very simple code example (adapted from the OP):

Repository:

 // it is only infrastructure if it doesn't know about specific types directly
    public Repository<T>
    {
       public T Find(int id)
       {
           // resolve the entity
           return default(T);   
       }
    }

Domain Entity:

public class MyComplexClass1
{
    MyComplexClass1 Property1 {get; }  // requred because cannot be set from outside
    MyComplexClass2 Property2 {get; set;}
    private readonly IService MyService {get; set;}

    // no dependency injection frameworks!
    public MyComplexClass1(MyComplexClass1 property1)
    {
       // actually using the constructor to define the required properties
       // MyComplexClass1 is required and MyComplexClass2 is optional
       this.Property1 = property1;
       .....
    }

    public ComplexCalculationResult CrazyComplexCalculation(MyComplexClass3 complexity)
    {
       var theAnswer = 42;
       return new ComplexCalculationResult(theAnswer);
    }
}

Controller (Application Service):

public class TheController : Controller
{
    private readonly IRepository<MyComplexClass1> complexClassRepository;
    private readonly IRepository<ComplexResult> complexResultRepository;

    // this can use IoC if needed, no probs
    public TheController(IRepository<MyComplexClass1> complexClassRepository, IRepository<ComplexResult> complexResultRepository)
    {
        this.complexClassRepository = complexClassRepository;
        this.complexResultRepository = complexResultRepository;
    }

    // I know about HTTP
    public void Post(int id, int value)
    {
        var entity = this.complexClassRepository.Find(id);

        var complex3 = new MyComplexClass3(value);
        var result = entity.CrazyComplexCalculation(complex3);

        this.complexResultRepository.Save(result);  
    }
}

Now, very quickly you will be thinking, "Woah, that Controller is doing too much". For example, how about if we need 50 values to construct MyComplexClass3. This is where the Onion Architecture is brilliant. There is a design pattern for that called Factory or Builder and without the constraints of application concerns or persistence concerns, you can implement it easily. So, you refactor into the domain these patterns (and they become your domain services).

In summary, nothing the domain calls knows about application or persistence concerns. It is the end, the core of the system.

Hope this makes sense, I wrote a little bit more than I intended. :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top