What is the difference between a Component and a Service dependency?
Asked Answered
B

1

6

I'm going to preface this question with the statement: I know the following is bad design, but refactoring is not currently an option, ideally it should be done using interceptors.

I am working on upgrading castle from 1.6 (I think) to 3.3 which unfortunately involves some syntax changes, I've got everything compiling now but some of my tests around the service container aren't working.

I have a repository that has several implementations to provide different functionality, the repository is only ever used with all of the different implementations inline, here are the basics of the code:

The Castle Windsor registrations:

RepositoryRegistration<IAccountRepository, AccountRepositoryFeedEntryDecorator>()
    .DependsOn(Dependency.OnComponent("decoratedRepository", typeof(AccountRepositoryAuthorizationDecorator))),
RepositoryRegistration<AccountRepositoryAuthorizationDecorator>()
    .DependsOn(Dependency.OnComponent("decoratedRepository", typeof(AccountRepositoryMaskingDecorator))),
RepositoryRegistration<AccountRepositoryMaskingDecorator>()
    .DependsOn(Dependency.OnComponent("decoratedRepository", typeof(AccountRepository))),
RepositoryRegistration<AccountRepository>());

The RepositoryRegistration method:

private static ComponentRegistration<TRepository> RepositoryRegistration<TRepository, TConcreteRepository>()
    where TConcreteRepository : TRepository where TRepository : class
{
    return Component
               .For<TRepository>()
               .ImplementedBy<TConcreteRepository>()                    
               .Named(typeof(TConcreteRepository).Name);
}

The base interface:

public interface IAccountRepository
{
    string Create(Account account);
    void Update(Account account);
    Account Get(string accountId);
}

The implementations:

public class AccountRepositoryFeedEntryDecorator : IAccountRepository
{
    private readonly IAccountRepository decoratedRepository;
    public AccountRepositoryFeedEntryDecorator(
        IAccountRepository decoratedRepository)
    {
        this.decoratedRepository = decoratedRepository;
    }

    string Create(Account account)
    {
        //Add Entry To Feed
        return decoratedRepository.Create(account);
    };

    void Update(Account account)
    {
        //Add Entry To Feed
        return decoratedRepository.Udpate(account);
    }
    Account Get(string accountId);
    {
        //Add Entry To Feed
        return decoratedRepository.Get(accountId);
    }
}

public class AccountRepositoryAuthorizationDecorator : IAccountRepository
{
    private readonly IAccountRepository decoratedRepository;
    public AccountRepositoryAuthorizationDecorator(
        IAccountRepository decoratedRepository)
    {
        this.decoratedRepository = decoratedRepository;
    }

    string Create(Account account)
    {
        //Ensure User Is Authorized
        return decoratedRepository.Create(account);
    };

    void Update(Account account)
    {
        //Ensure User Is Authorized
        return decoratedRepository.Udpate(account);
    }
    Account Get(string accountId);
    {
        //Ensure User Is Authorized
        return decoratedRepository.Get(accountId);
    }
}

public class AccountRepositoryMaskingDecorator : IAccountRepository
{
    private readonly IAccountRepository decoratedRepository;
    public AccountRepositoryMaskingDecorator(
        IAccountRepository decoratedRepository)
    {
        this.decoratedRepository = decoratedRepository;
    }

    string Create(Account account)
    {
        //Mask Sensitive Information
        return decoratedRepository.Create(account);
    };

    void Update(Account account)
    {
        //Mask Sensitive Information
        return decoratedRepository.Udpate(account);
    }
    Account Get(string accountId);
    {
        //Mask Sensitive Information
        return decoratedRepository.Get(accountId);
    }
}

public class AccountRepository : IAccountRepository
{       
    string Create(Account account)
    {
        //Create account and return details
    };

    void Update(Account account)
    {
        //Update account and return details
    }
    Account Get(string accountId);
    {
        //Return Account
    }
}

And finally here is the error I'm getting in my test:

Castle.MicroKernel.Handlers.HandlerException : Can't create component 'AccountRepositoryFeedEntryDecorator' as it has dependencies to be satisfied.

'AccountRepositoryFeedEntryDecorator' is waiting for the following dependencies: - Component 'Shaw.Services.CustomerManagement.Host.Repositories.Sql.Decorators.AccountRepositoryAuthorizationDecorator' (via override) which was registered but is also waiting for dependencies.

'Shaw.Services.CustomerManagement.Host.Repositories.Sql.Decorators.AccountRepositoryAuthorizationDecorator' is waiting for the following dependencies: - Service 'AccountRepositoryFeedEntryDecorator' which was registered but is also waiting for dependencies.

At first glance it appears there is some kind of circular dependency happening, but I can't really see how.

So the question in two parts, what is the difference between the component and service dependency specifications in the error message, any guesses as to what is going wrong.

If it matters here is the original registration before the upgrade:

RepositoryRegistration<IAccountRepository, AccountRepositoryFeedEntryDecorator>()
    .ServiceOverrides(new { decoratedRepository = typeof(AccountRepositoryAuthorizationDecorator).Name }),
RepositoryRegistration<AccountRepositoryAuthorizationDecorator>()
    .ServiceOverrides(new { decoratedRepository = typeof(AccountRepositoryMaskingDecorator).Name }),
RepositoryRegistration<AccountRepositoryMaskingDecorator>()
    .ServiceOverrides(new { decoratedRepository = typeof(AccountRepository).Name }),
RepositoryRegistration<AccountRepository>()
Benuecongo answered 2/9, 2014 at 19:52 Comment(0)
I
6

Decorator registration occurs in registration order, and you don't need to specify dependencies, so this will work like you'd expect:

container.Register(
    RepositoryRegistration<IAccountRepository, AccountRepositoryFeedEntryDecorator>(),
    RepositoryRegistration<IAccountRepository, AccountRepositoryAuthorizationDecorator>(),
    RepositoryRegistration<IAccountRepository, AccountRepositoryMaskingDecorator>(),
    RepositoryRegistration<IAccountRepository, AccountRepository>()
);

Resolving an instance of IAccountRepository will yield a AccountRepositoryFeedEntryDecorator, which decorates a AccountRepositoryAuthorizationDecorator, etc.


As to your question, this page does a great job explaining the differences between services, components, and dependencies as the terms are used within the library. Essentially:

  • Service is some contract of functionality, typically an interface or delegate. It is abstract.
  • Component is an implementation of a service, typically a class. It is concrete.
  • Dependency is a service that a component uses.

In your error message, the first bit is:

Castle.MicroKernel.Handlers.HandlerException : Can't create component 'AccountRepositoryFeedEntryDecorator' as it has dependencies to be satisfied.

Ok, so the component/class cannot be created because its dependencies couldn't be satisfied. It's dependency is the IAccountRepository decoratedRepository parameter in the constructor.

'AccountRepositoryFeedEntryDecorator' is waiting for the following dependencies: - Component 'AccountRepositoryAuthorizationDecorator' (via override) which was registered but is also waiting for dependencies.

We're still talking about the same component/class, and it's saying it was trying to use the component/class AccountRepositoryAuthorizationDecorator to fulfill its dependency, but that class also has dependencies.

'AccountRepositoryAuthorizationDecorator' is waiting for the following dependencies: - Service 'AccountRepositoryFeedEntryDecorator' which was registered but is also waiting for dependencies.

And we've come around to the first class, so there is a circular dependency. This is because of a disconnect between the name set in RepositoryRegistration and the name computed when you pass in a type to Dependency.OnComponent. For the former, you're using Type.Name (i.e., "AccountRepositoryFeedEntryDecorator"), and for the latter Windsor uses Type.FullName under the covers (i.e., "Your.Assembly.Name, AccountRepositoryFeedEntryDecorator").

Because of the naming mismatch, when Windsor tries to fulfill the dependency that you specified it will miss the registration for it because it has a different name. As a result it does the best it can with no information and (I assume) finds the first IAccountRepository--that isn't the component itself--it can to insert as the dependency. This happens again with that dependency, which is the first component we started with, giving a circular dependency.

You could have solved this by removing the Named portion of the registration, or by passing typeof(AccountRepositoryAuthorizationDecorator).Name to Dependency.OnComponent instead of the type itself.

Intraatomic answered 2/9, 2014 at 20:13 Comment(6)
Hi Patrick, I just tried this solution but now I get an error that first complains that it is pointing back to the service, then says there are other components that match and goes on to have the same 'circular' dependency issue.Benuecongo
I take that back on second glance it looks like a completely different registration issue altogether.Benuecongo
@Phaeze Ah ok, well if it's related feel free to update your question, otherwise it may make sense as a new question altogether.Intraatomic
OK got the other issue fixed and all my tests are passing now. And my registration code is so much cleaner. Thanks for pointing this out.Benuecongo
@Phaeze Somewhat academic at this point, but see updated answer.Intraatomic
Wow good explanation it all makes perfect sense now, wish I could upvote this more :)Benuecongo

© 2022 - 2024 — McMap. All rights reserved.