4

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?
Arpit Khandelwal
  • 1,743
  • 3
  • 23
  • 34
  • I find it hard to understand the problem because it's too abstract (everything's an interface). I don't really see what "specific domain functionality" you are exposing, apart from the `currentUser.Name`. Your question 3 is too broad. Designs are compromises of all sorts, so the answer is you most likely (in a complex project) violate SOLID somewhere. TL;DR Your question needs to be more specific to get answers. – Fuhrmanator Mar 05 '14 at 15:48
  • SolutionDomain class represents specific domain functionality which is exposed as domain.SolutionDomain – Arpit Khandelwal Mar 06 '14 at 05:09

1 Answers1

4

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.

Community
  • 1
  • 1
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Thanks @Mark. It was helpful. But my ServiceController (now ContentController) needs much more than ServiceDomain alone. It needs functionality from SolutionDomain, PhaseDomain and more. And I am not sure if these many Domain types should be injected in a my ContentController. This was the reason I wrapped all of the specific domain types in ContentDomain class. Any help here? See my edits: ServiceController renamed to ContentController. – Arpit Khandelwal Mar 06 '14 at 10:33
  • are you suggesting to have separate consumers (Controllers) for ServiceDomain, SolutionDomain and PhaseDomain in my client application? Yes, that way it moves closer to ISP (and SRP) but then it becomes an ASP.NET MVC specific problem. – Arpit Khandelwal Mar 06 '14 at 10:48
  • 3
    If your Controllers have too many dependencies, you would like to know about it, because that's a *problem* (it indicates that SRP is violated). That's the second bullet point above. Hiding many dependencies behind `IContentDomain` doesn't *solve* that problem: it only hides it; it makes it harder to discover that you have a problem in the first place. It sounds like you could benefit from introducing a Facade Service: http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices – Mark Seemann Mar 06 '14 at 11:37
  • Great answer with handful of precious links exactly like `Facade Services` – Wasif Hossain Mar 06 '14 at 13:49
  • @MarkSeemann, Nice article on aggregating. But I feel more inclined towards isolating every domain functionality at the client into separate controller. For example- SolutionController with ISolutionDomain injection. – Arpit Khandelwal Mar 07 '14 at 03:12
  • @ArpitKhandelwal I don't know the details of your domain or code base, but if you can do that, it also sounds like a good approach. – Mark Seemann Mar 07 '14 at 06:34