0

So I have a typical three tiered application layered as below

DAL -> Repository -> Business -> Web.UI/API

I have been reading this article about registering dependencies by centralizing them via modules.

The web layer only has a reference to Business which only has a reference to the Repo which only has a reference to the lowest DAL layer. In this topology since the UI/API layer knows nothing about the Repository and has no reference to it, I can't register the modules in the Repository in the UI/API layer. Similarly I can't register the modules present in the DAL in the Business layer. What I want to do is start the registration process in the top most layer which then sets off a cascading effect of registrations in subsequent layers.

Typically what this would look like is each layer exposing a RegisterAllModules method and somehow trigger the RegisterAllModules method from the layer below it. Has something like this been done? Or is there another way to do this? At this point I don't know if I should roll my own logic out as I mentioned here above, since I don't know if there is a documented way to do something like this or not. Thoughts on how to best go forward here is what I am looking for.

Thanks.

user20358
  • 14,182
  • 36
  • 114
  • 186
  • Have you already looked at this section of the Autofac Manual? http://docs.autofac.org/en/latest/register/scanning.html#scanning-for-modules – Eris Jan 12 '16 at 17:59
  • Yes, but I think that would mean all my layers to be within the same assembly. I am looking at the Composition Root approach to solve this problem now. – user20358 Jan 14 '16 at 07:39
  • @user20358 The composition root should have access to all layers that need wiring up, since the composition root wires it all up. – Maarten Jan 14 '16 at 15:17
  • @user20358 If you really want the registration to take place using modules-per-layer, than that also means that all layers will need to know about your DI-tool (autofac), which also is not ideal. It is pretty common to have direct access to all layers from the composition root. – Maarten Jan 14 '16 at 15:19

1 Answers1

2

Mmmm... I don't know if what follows is a proper response, but I'm going to try to give you the tools for a solution that suits your exact requirementes.

  • have you looked into json/xml module configuration? You do not need to know the assemblies through cross reference, you just need to know the name of the assemblies in app.config (or web.config). E.g: you can register one module for Repositories in the Repo assembly and one module for Business services in the Business.dll. This completely removes the need of cross-referencing the various assemblies (for Module scanning, you will still need references for method calls, but that is expected anyway). See here for details: http://docs.autofac.org/en/latest/configuration/xml.html#configuring-with-microsoft-configuration
  • if you want to enforce no call is done from (say) UI to Repo, you can leverage the "Instance Per Matching Lifetime Scope" function (see http://docs.autofac.org/en/latest/lifetime/instance-scope.html#instance-per-matching-lifetime-scope). You can use that registration method in order to enforce a Unit-of-work approach. E.g: a Repository can only be resolved in a "repository" LifetimeScope, and only Business components open scopes tagged "repository".
  • an alternative approach to tagged scopes is in using the "Instance per Owned<>" pattern. In this way, each Business service would require an Owned<Repository>. Something like:

    var builder = new ContainerBuilder(); builder.RegisterType(); builder.RegisterType().InstancePerOwned();

AFAICT, a correct approach would be to register the components through Modules, referenced by the Json/Xml config, and each Module should target specific LifetimeScopes. When you a class calls the underlying layer, it should open a new LifetimeScope("underlying layer").

I will elaborate further, if you want advice on implementation strategies.

Best,

Alberto Chiesa

Edit:

I didn't knew the "composition root" meaning. Well, thanks for the info! I favor a SIMPLE configuration file (be it the .config file or a separate .json or .xml file), because I feel that a list of modules to be imported is simpler done through a list than through a class. But this is opinion. What is not an opinion is that you can import modules from assembly that are not referenced by the "Composition Root" assembly, in a simple and tested way.

So, I would go for Modules for every component registration, but for a textual configuration file for Module registration. YMMV.

Now, let me show you an example of the Unit of Work pattern that I'm using in many live projects.

In our architecture we make heavy use of a Service Layer, which holds responsibility for opening connections to the db and disposing them when finished, etc. It's a simpler design than what you're after (I prefer shallow other than deep), but the concept is the same.

If you are "out" of the Service Layer (e.g. in an MVC Controller, or in the UI), you need a ServiceHandle in order to access the Service layer. The ServiceHandle is the only class that knows about Autofac and is responsible for service resolution, invocation and disposal.

The access to the Service Layer is done in this way:

  • non service classes can require only a ServiceHandle
  • invocation is done through _serviceHandle.Invoke(Func)
  • Autofac injects the ready to use handles via constructor injection.

This is done through the use of BeginLifetimeScope(tag) method, and registering services (in a module) in this way:

// register every service except for ServiceBase
Builder.RegisterAssemblyTypes(_modelAssemblies)
    .Where(t => typeof(IService).IsAssignableFrom(t) && (t != typeof(ServiceBase)))
    .InstancePerDependency();

// register generic ServiceHandle
Builder.RegisterGeneric(typeof(ServiceHandle<>))
    .AsSelf()
    .AsImplementedInterfaces()
    .InstancePerDependency();

And registering every shared resource as InstancePerMatchingLifetimeScope("service")

So, an example invocation would be:

... in the constructor:
public YourUiClass(ServiceHandle<MyServiceType> myserviceHandle)
{
  this._myserviceHandle = myserviceHandle;
}

... in order to invoke the service:
var result = _myserviceHandle.Invoke(s => s.myServiceMethod(parameter));

This is the ServiceHandle implementation:

/// <summary>
/// Provides a managed interface to access Model Services
/// </summary>
/// <typeparam name="TServiceType">The Type of the parameter to be managed</typeparam>
public class ServiceHandle<TServiceType> : IServiceHandle<TServiceType> where TServiceType : IService
{
    static private readonly ILog Log = LogManager.GetLogger(typeof(ServiceHandle<TServiceType>));

    private readonly ILifetimeScope _scope;

    /// <summary>
    /// True if there where Exceptions caught during the last Invoke execution.
    /// </summary>
    public bool ErrorCaught { get; private set; }

    /// <summary>
    /// List of the errors caught during execution
    /// </summary>
    public List<String> ErrorsCaught { get; private set; }

    /// <summary>
    /// Contains the exception that was thrown during the
    /// last Invoke execution.
    /// </summary>
    public Exception ExceptionCaught { get; private set; }

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="scope">The current Autofac scope</param>
    public ServiceHandle(ILifetimeScope scope)
    {
        if (scope == null)
            throw new ArgumentNullException("scope");

        _scope = scope;

        ErrorsCaught = new List<String>();
    }

    /// <summary>
    /// Invoke a method to be performed using a 
    /// service instance provided by the ServiceHandle
    /// </summary>
    /// <param name="command">
    /// Void returning action to be performed
    /// </param>
    /// <remarks>
    /// The implementation simply wraps the Action into
    /// a Func returning an Int32; the returned value
    /// will be discarded.
    /// </remarks>
    public void Invoke(Action<TServiceType> command)
    {
        Invoke(s =>
        {
            command(s);
            return 0;
        });
    }

    /// <summary>
    /// Invoke a method to be performed using a 
    /// service instance provided by the ServiceHandle
    /// </summary>
    /// <typeparam name="T">Type of the data to be returned</typeparam>
    /// <param name="command">Action to be performed. Returns T.</param>
    /// <returns>A generically typed T, returned by the provided function.</returns>
    public T Invoke<T>(Func<TServiceType, T> command)
    {
        ErrorCaught = false;
        ErrorsCaught = new List<string>();
        ExceptionCaught = null;

        T retVal;

        try
        {
            using (var serviceScope = GetServiceScope())
            using (var service = serviceScope.Resolve<TServiceType>())
            {
                try
                {
                    retVal = command(service);

                    service.CommitSessionScope();
                }
                catch (RollbackException rollbackEx)
                {
                    retVal = default(T);

                    if (System.Web.HttpContext.Current != null)
                        ErrorSignal.FromCurrentContext().Raise(rollbackEx);

                    Log.InfoFormat(rollbackEx.Message);

                    ErrorCaught = true;
                    ErrorsCaught.AddRange(rollbackEx.ErrorMessages);
                    ExceptionCaught = rollbackEx;

                    DoRollback(service, rollbackEx.ErrorMessages, rollbackEx);
                }
                catch (Exception genericEx)
                {
                    if (service != null)
                    {
                        DoRollback(service, new List<String>() { genericEx.Message }, genericEx);
                    }

                    throw;
                }
            }
        }
        catch (Exception ex)
        {
            if (System.Web.HttpContext.Current != null)
                ErrorSignal.FromCurrentContext().Raise(ex);

            var msg = (Log.IsDebugEnabled) ?
                String.Format("There was an error executing service invocation:\r\n{0}\r\nAt: {1}", ex.Message, ex.StackTrace) :
                String.Format("There was an error executing service invocation:\r\n{0}", ex.Message);

            ErrorCaught = true;
            ErrorsCaught.Add(ex.Message);
            ExceptionCaught = ex;

            Log.ErrorFormat(msg);

            retVal = default(T);
        }

        return retVal;
    }

    /// <summary>
    /// Performs a rollback on the provided service instance
    /// and records exception data for error retrieval.
    /// </summary>
    /// <param name="service">The Service instance whose session will be rolled back.</param>
    /// <param name="errorMessages">A List of error messages.</param>
    /// <param name="ex"></param>
    private void DoRollback(TServiceType service, List<string> errorMessages, Exception ex)
    {
        var t = new Task<string>
        service.RollbackSessionScope();
    }

    /// <summary>
    /// Creates a Service Scope overriding Session resolution:
    /// all the service instances share the same Session object.
    /// </summary>
    /// <returns></returns>
    private ILifetimeScope GetServiceScope()
    {
        return _scope.BeginLifetimeScope("service");
    }
}

Hope it helps!

Alberto Chiesa
  • 7,022
  • 2
  • 26
  • 53
  • Thanks Alberto for taking the time to suggest these different approaches. I would definitely like to see an implementation of what you have talked about. It always helps to have another approach to doing the same thing. I have also stumbled upon the Composition Root approach which I think does what I want to have done. Managing scopes is also something I would like to achieve using this approach and if not possible using this approach, then I might have to fall back on what you have mentioned above. – user20358 Jan 14 '16 at 07:44