Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

Here is my Solution and projects:

  • BookStore (solution)
    • BookStore.Coupler (project)
      • Bootstrapper.cs
    • BookStore.Domain (project)
      • CreateBookCommandValidator.cs
      • CompositeValidator.cs
      • IValidate.cs
      • IValidator.cs
      • ICommandHandler.cs
    • BookStore.Infrastructure (project)
      • CreateBookCommandHandler.cs
      • ValidationCommandHandlerDecorator.cs
    • BookStore.Web (project)
      • Global.asax
    • BookStore.BatchProcesses (project)
      • Program.cs

Bootstrapper.cs:

public static class Bootstrapper.cs 
{
    // I'm using SimpleInjector as my DI Container
    public static void Initialize(Container container) 
    {
        container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), typeof(CreateBookCommandHandler).Assembly);
        container.RegisterDecorator(typeof(ICommandHandler<>), typeof(ValidationCommandHandlerDecorator<>));
        container.RegisterManyForOpenGeneric(typeof(IValidate<>),
            AccessibilityOption.PublicTypesOnly,
            (serviceType, implTypes) => container.RegisterAll(serviceType, implTypes),
            typeof(IValidate<>).Assembly);
        container.RegisterSingleOpenGeneric(typeof(IValidator<>), typeof(CompositeValidator<>));
    }
}

CreateBookCommandValidator.cs

public class CreateBookCommandValidator : IValidate<CreateBookCommand>
{
    public IEnumerable<IValidationResult> Validate(CreateBookCommand book)
    {
        if (book.Author == "Evan")
        {
            yield return new ValidationResult<CreateBookCommand>("Evan cannot be the Author!", p => p.Author);
        }
        if (book.Price < 0)
        {
            yield return new ValidationResult<CreateBookCommand>("The price can not be less than zero", p => p.Price);
        }
    }
}

CompositeValidator.cs

public class CompositeValidator<T> : IValidator<T>
{
    private readonly IEnumerable<IValidate<T>> validators;

    public CompositeValidator(IEnumerable<IValidate<T>> validators)
    {
        this.validators = validators;
    }

    public IEnumerable<IValidationResult> Validate(T instance)
    {
        var allResults = new List<IValidationResult>();

        foreach (var validator in this.validators)
        {
            var results = validator.Validate(instance);
            allResults.AddRange(results);
        }
        return allResults;
    }
}

IValidate.cs

public interface IValidate<T>
{
    IEnumerable<IValidationResult> Validate(T instance);
}

IValidator.cs

public interface IValidator<T>
{
    IEnumerable<IValidationResult> Validate(T instance);
}

ICommandHandler.cs

public interface ICommandHandler<TCommand>
{
    void Handle(TCommand command);
}

CreateBookCommandHandler.cs

public class CreateBookCommandHandler : ICommandHandler<CreateBookCommand>
{
    private readonly IBookStore _bookStore;

    public CreateBookCommandHandler(IBookStore bookStore)
    {
        _bookStore = bookStore;
    }

    public void Handle(CreateBookCommand command)
    {
        var book = new Book { Author = command.Author, Name = command.Name, Price = command.Price };
        _bookStore.SaveBook(book);
    }
}

ValidationCommandHandlerDecorator.cs

public class ValidationCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decorated;
    private readonly IValidator<TCommand> validator;

    public ValidationCommandHandlerDecorator(ICommandHandler<TCommand> decorated, IValidator<TCommand> validator)
    {
        this.decorated = decorated;
        this.validator = validator;
    }

    public void Handle(TCommand command)
    {
        var results = validator.Validate(command);

        if (!results.IsValid())
        {
            throw new ValidationException(results);
        }

        decorated.Handle(command);
    }
}

Global.asax

// inside App_Start()
var container = new Container();
Bootstrapper.Initialize(container);
// more MVC specific bootstrapping to the container. Like wiring up controllers, filters, etc..

Program.cs

// Pretty much the same as the Global.asax

Sorry for the long setup to the problem, I have no better way of explaining this other than detailing my actual problem.

I don't want to make my CreateBookCommandValidator public. I would rather it be internal but if I make it internal then I will not be able to register it with my DI Container. The reason I would like it to be internal is because the only project that should have notion of my IValidate<> implementations are in the BookStore.Domain project. Any other project just needs to consume IValidator<> and the CompositeValidator should be resolved which will fulfill all validations.

How can I use Dependency Injection without breaking encapsulation? Or am I going about this all wrong?

share|improve this question

migrated from stackoverflow.com Dec 30 '13 at 22:36

This question came from our site for professional and enthusiast programmers.

    
Just a nitpick: What you are using is not a correct command pattern, so calling it command might be misinformation. Also, CreateBookCommandHandler seems like it is breaking LSP: what will happen, if you pass object, that derives from CreateBookCommand? And I think what you are doing here is actually Anemic Domain Model antipattern. Things like saving should be inside the domain and validation should be part of entity. –  Euphoric Dec 31 '13 at 7:42
1  
@Euphoric: That's correct. This is not the command pattern. As a matter of fact, the OP follows a different pattern: the command/handler pattern. –  Steven Dec 31 '13 at 12:04
    
There were so many good answers i wish I could have marked more as the answer. Thanks everyone for your help. –  Evan Larsen Dec 31 '13 at 12:25
    
@Euphoric, after re-thinking the project layout I think the CommandHandlers should be in the Domain. Not sure why I put them in the Infrastructure project. Thanks. –  Evan Larsen Dec 31 '13 at 18:26

5 Answers 5

up vote 7 down vote accepted

Making the CreateBookCommandValidator public does not violate encapsulation, since

Encapsulation is used to hide the values or state of a structured data object inside a class, preventing unauthorized parties direct access to them (wikipedia)

Your CreateBookCommandValidator doesn't allow access to its data members (it currently doesn't seem to have any) so its not violating encapsulation.

Making this class public does not violate any other principle (such as the SOLID principles), because:

  • That class has a single well defined responsibility and therefore follows the Single Responsibility Principle.
  • Adding new validators to the system can be done without changing a single line of code and you therefore follow the Open/Closed Principle.
  • That IValidator<T> interface that this class implements is narrow (has only one member) and follows the Interface Segregation Principle.
  • Your consumers only depend on that IValidator<T> interface and therefore follow the Dependency Inversion Principle.

You can only make the CreateBookCommandValidator internal if the class isn't consumed directly from outside the library, but this hardly ever is the case, since your unit tests are an important consumer of this class (and almost every class in your system).

Although you can make the class internal and use [InternalsVisibleTo] to allow the unit test project to access your project's internals, why bother?

The most important reason to make classes internal is to prevent external parties (that you don't have control over) to take a dependency on such class, because that would prevent you from making future changing to that class without breaking anything. In other words, this only holds when you're creating a reusable library (such as a dependency injection framework). As a matter of fact, the Simple Injector framework contains internal stuff and its unit test project tests these internals.

However, if you're not creating a reusable project, this problem does not exist. It does not exist, because you can change the projects that depend on it, and the other developers in your team will have to follow your guidelines. And one simple guideline will do: Program to an abstraction; not an implementation (the Dependency Inversion Principle).

So long story short, don't make this class internal unless you are writing a reusable library.

But if you still want to make this class internal, you can still register it with Simple Injector without any problem like this:

container.RegisterManyForOpenGeneric(typeof(IValidate<>),
    AccessibilityOption.AllTypes,
    container.RegisterAll,
    typeof(IValidate<>).Assembly);

The only thing to make sure of is that your all validators have a public constructor, even though they are internal.

share|improve this answer
    
That makes sense, thank you. –  Evan Larsen Dec 31 '13 at 12:28

It is not a big deal that the CreateBookCommandValidator class is public.

If you need to create instances of it outside of the library that defines it, it is a pretty natural approach to expose the public class, and count on the clients only using that class as an implementation of IValidate<CreateBookCommand>. (Simply exposing a type does not mean that encapsulation is broken, it just makes it a bit easier for clients to break encapsulation).

Otherwise, if you really want to force clients to not know about the class, you can also use a public static factory method instead of exposing the class, e.g.:

public static class Validators
{
    public static IValidate<CreateBookCommand> NewCreateBookCommandValidator()
    {
        return new CreateBookCommnadValidator();
    }
}

As for registering in your DI container, all DI containers that I know of allow construction by using a static factory method.

share|improve this answer
    
Yes, thank you.. I was originally thinking the same thing before I created this post. I was thinking about making a Factory class that would return back the appropriate IValidate<> implementation but if any of the IValidate<> implementations had any dependencies, it would probably get pretty hairy quickly. –  Evan Larsen Dec 31 '13 at 18:31
    
@EvanLarsen Why? If the IValidate<> implementation has dependencies, then put these dependencies as parameters to the factory method. –  jhominal Dec 31 '13 at 19:00

You can declare CreateBookCommandValidator as internal an apply the InternalsVisibleToAttribute in order to make it visible to the BookStore.Coupler assembly. This also often helps when making unit tests.

share|improve this answer
    
I had no idea of the existence of that attribute. Thank you –  Evan Larsen Dec 31 '13 at 18:32

You could make it internal and use the InternalVisibleToAttribute msdn.link so your testing framework/project can access it.

I had a related problem -> link.

Here is a link to another Stackoverflow question regarding the problem:

And finally an article on the web.

share|improve this answer
    
I had no idea of the existence of that attribute. Thank you –  Evan Larsen Dec 31 '13 at 18:32

Another option is to make it public but put it in another assembly.

So essentially, you have a service interfaces assembly, a service implementations assembly (which references service interfaces), a service consumer assembly (which references service interfaces), and an IOC registrar assembly (which references both service interfaces and service implementations to hook them together).

I should stress, this is not always the most appropriate solution, but it is one worth considering.

share|improve this answer
    
Would that remove the sligth security risk of making internals visible? –  Johannes Jan 2 at 21:36
1  
@Johannes: Security risk? If you're relying on access modifiers to give you security, you need to worry. You can get access to any method via reflection. But it does remove easy/encouraged access to internals by putting the implementation in another assembly that isn't referenced. –  pdr Jan 3 at 11:03

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.