简体   繁体   中英

Design pattern choice for domain/business layer

I am trying to avoid this class ContentDomain becoming a God class and isolating the functionality into specific classes (to follow SRP) like this

ContentDomain :

 public class ContentDomain : IContentDomain
{
    private ISolutionDomain solutionDomain;
    private IServiceDomain serviceDomain;
    private IPhaseDomain phaseDomain;

    public ContentDomain(IUnitOfWork _unitOfWork)
    {
        this.solutionDomain = new SolutionDomain(_unitOfWork);
        this.serviceDomain = new ServiceDomain(_unitOfWork);
        this.phaseDomain = new PhaseDomain(_unitOfWork);
    }

    public ISolutionDomain SolutionDomain { get { return solutionDomain; } }
    public IServiceDomain ServiceDomain { get { return serviceDomain; } }
    public IPhaseDomain PhaseDomain { get { return phaseDomain; } }
}

One of the specific domain classes

public class SolutionDomain : BaseDomain, ISolutionDomain
{
    public SolutionDomain(IUnitOfWork _unitOfWork)
        : base(_unitOfWork)
    {

    }

    public IEnumerable<Solution> GetAllSolutions()
    {
        return base.GetAll<Solution>(sol => sol.IsActive == true).OrderBy(rec => rec.Name).Select(rec => rec).ToList();
    }
}

And now my controller is only aware of ContentDomain and calls specific methods from SolutionDomain/ServiceDomain/PhaseDomain as and when needed:

public ContentController(IContentDomain domain, ICurrentUser currentUser)
        : base(domain, currentUser)
    {

    }


public ActionResult Home()
    {
        var myServices = domain.ServiceDomain.GetServicesWithDetails(rec => rec.CreatedBy == currentUser.Name);
        var viewModelCollection = myServices.Select(service => new DashboardViewModel(service, domain));

        if (currentUser.IsInRole("SU"))
            return View("Home_SU", viewModelCollection);

        else if (currentUser.IsInRole("Reviewer"))
            return View("Home_Reviewer", viewModelCollection);

        else return View("Home", viewModelCollection);
    }

Notice the first statement in Home()

domain.ServiceDomain.GetServicesWithDetails(rec => rec.CreatedBy == currentUser.Name);

I find myself mixing Facade and Composition in ContentDomain class.

Now the questions are-

  1. Is it reasonable to expose specific domain functionality thru Facade using composition?
  2. If not, what could be the catch?
  3. Chances I am violating any of the SOLID principles with this approach?

Is it reasonable to expose specific domain functionality thru Facade using composition?

Based on the example, the ContentDomain class and the IContentDomain interface provide no functionality. A better form of composition would be to throw away both, and define Controllers and other clients based on the minimal set of dependencies they require:

private readonly IServiceDomain serviceDomain;
private readonly ICurrentUser currentUser;

public ServiceController(IServiceDomain serviceDomain, ICurrentUser currentUser)
{
    this.serviceDomain = serviceDomain;
    this.currentUser = currentUser;
}

public ActionResult Home()
{
    var myServices = this.serviceDomain.GetServicesWithDetails(
        rec => rec.CreatedBy == currentUser.Name);
    var viewModelCollection = myServices.Select(
        service => new DashboardViewModel(service, domain));

    if (this.currentUser.IsInRole("SU"))
        return View("Home_SU", viewModelCollection);

    else if (this.currentUser.IsInRole("Reviewer"))
        return View("Home_Reviewer", viewModelCollection);

    else return View("Home", viewModelCollection);
}

This is true Composition , because you compose ServiceController with implementations of IServiceDomain and ICurrentUser .

If not, what could be the catch?

There are several problems with the design of IContentDomain .

  • It's more difficult to maintain, because every time you want to add another service to IContentDomain , you'll need to add it as a (read-only) property to the interface, and that's a breaking change.
  • It may hide that there are more dependencies in play than what's immediately apparent. Looking at the proposed constructor of ServiceController , it looks as though only two dependencies are passed into ServiceController , but the actual number is four. One of the great benefits of flat Constructor Injection is that it makes it quite clear when the Single Responsibility Principle is violated .
  • It violates the Interface Segregation Principle (see below).

Chances I am violating any of the SOLID principles with this approach?

Yes, this design violates SOLID because, at the very least, it violates the Interface Segregation Principle, which states that clients should not be forced to depend on members they do not use .

However, in the above example, ServiceController is being forced to depend on the SolutionDomain and PhaseDomain properties, although it doesn't use it.

It's also very likely that this design will lead to a violation of the Single Responsibility principle, because the more functionality you pass to a client, the more it would be inclined to do by itself, instead of relying on other parts of the system.

It's also likely that it'll lead to a violation of the Liskov Substitution Principle (LSP), because there's a general tendency that the more members you define on an interface, the harder it becomes to adhere to the LSP. Usually, Header Interfaces tend to lead to LSP violations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM