AppBoot Library
AppBoot is a generic .NET application bootstrapper, we at iQuarc, have put on GitHub a while ago. It started few years back when we were about to begin developing a large enterprise application. Back then, we wanted to use the Unity Container for dependency injection, but we liked the way
AppBoot is a generic .NET application bootstrapper, we at iQuarc, have put on GitHub a while ago.
It started few years back when we were about to begin developing a large enterprise application. Back then, we wanted to use the Unity Container for dependency injection, but we liked the way MEF was declaring, with attributes, the interfaces and their implementation. The next day my colleague Cristian put in some code which did exactly that: bring the MEF like attributes for configuring dependency injection to Unity. Since then, it got refined and we've started to see many other benefits from using the AppBoot.
Now, most of the projects we begin start with the AppBoot as the first piece of infrastructure.
What Is iQuarc.AppBoot
A lightweight library that handles the startup of any .NET application. It also abstracts the composite application as concept.
AppBoot is a reusable implementation of the Separate Configuration and Construction from Use principle.
The two most important steps performed at application startup are:
- configure the Dependency Injection Container, and
- initialize the Composite Application
AppBoot is not a dependency injection container by itself. It uses one. It abstracts and hides the underlying container.
One of the most common questions I get when introducing it is:
Why use the AppBoot and not the Dependency Injection framework directly?
My answer is that by hiding the dependency injection framework from the rest of your code you gain consistency. You have consistency on how DI is done (only through constructor or only against interfaces, etc.) and you know that DI is used on all of your code. It is the same technique that I describe in my Enforce Consistency with Assembly References post.
There are also other benefits that arise from using the AppBoot. I will detail the most relevant in the rest of the post. Some are like adding the missing features to the DI framework and having a central place where such features can be added, others are in the support it brings on applying and following design patterns and programming principles.
Where Is AppBoot Available
You can find all the source code on GitHub here. If you go through the read.me
file you will get the basics on how to use it. You'll see examples for the Annotation Based Configuration, for the Convention Based Configuration for the Instances Lifetime and for the Composite Application support. I won't repeat them here.
The AppBoot design is extensible. You can bring in the Dependency Injection framework that you like, by implementing the IDependencyContainer
interface with an adapter for it. You can also implement new conventions on how to configure the dependency injection by adding more implementation of IRegistrationBehavior
interface.
It is also available as NuGet Packages:
- iQuarc.AppBoot - the core library, which does not depend on any type of .NET application, nor on a specific dependency injection framework
- iQuarc.AppBoot.Unity - the adapters to use it with Unity Container
- iQuarc.AppBoot.WebApi - the helpers to configure it for an ASP.NET Web API project
What AppBoot Provides
Even if the AppBoot is a lightweight library, and it has a very simple code, when combined with some principles it may be very powerful in some scenarios. It supports the implementation of some good design principles like Separation of Concerns, Modularity and Loose Coupled Implementations. Its most valuable benefits detailed below, are very much connected and derive one from the other.
Dependencies Discovery
Instead of explicit registration of the pairs between Service Contract (interface) and Service Implementation (class), AppBoot promotes a way to discover them at application startup.
These pairs can be specified in a declarative manner, with attributes like:
[Service(typeof (IPriceCalculator), Lifetime.AlwaysNew)]
public class PriceCalculator : IPriceCalculator
{
...
}
The ServiceAttribute
says that the class it decorates, implements the interface it has in the constructor. Above, PriceCalculator
class implements the IPriceCalculator
interface.
It also specifies that the lifetime is AlwaysNew
, meaning that a new instance is created each time when it is injected as a dependency to another implementation. At the other end is the Lifetime.Application
which is equivalent to Singleton.
By making this kind of declaration on the implementation class, not only that we get rid of the long registration config files, but the lifetime specification is very close to the implementation. This may help in avoiding implementation mistakes. For example, if we say that this implementation is Lifetime.Application
(Singleton) and it is statefull we'd better synchronize it in a multi-thread environment.
We could also specify such a pair through conventions, like:
conventions.ForTypesDerivedFrom<Repository>()
.ExportInterfaces(i => i.Name.EndsWith("Repository"))
.WithLifetime(Lifetime.AlwaysNew);
This says that all the classes that inherit the base Repository
class should be registered as implementations of the specific interface they implement. Like: for PersonsRepository : Repository, IPersonsRepository
the (IPersonsRepository
, PersonsRepository
) pair should be registered.
At application startup, on Bootstrapper.Run()
, AppBoot will scan with reflection all the types from all the assemblies that build our application, will look after the types that have the ServiceAttribute
or match a convention and will make the registrations into the DI Container.
Modular Application
AppBoot defines an application as being composed by a set of modules. The modules are defined by a simple interface:
public interface IModule
{
void Initialize();
}
We can create any number of modules as implementations of the IModule
interface and declare them as such. For example:
[Service(nameof(MyModule), typeof(IModule))]
public class MyModule : IModule
{
public void Initialize()
{
// some configuration code that runs at app startup
}
}
At application startup, on Bootstrapper.Run()
, AppBoot will find all the modules and for each of them will execute their Initialize()
method. Therefore, if we have some (configuration) code that needs to run on the application startup, the AppBoot defines where that code should be and it takes care of running it.
Separation between Configuration and Construction from Use
AppBoot delegates everything related to constructing object instances to the underneath Dependency Injection framework. By hiding it from the rest of the code it assures a separation between its configuration and the rest of the code.
For example, the application code cannot call functions of the Unity Container to do additional registrations mixed with business logic, because it doesn't reference it. It can only make declarations of interface - implementation pairs, in a declarative manner through the AppBoot.
Moreover, with the modules support the AppBoot brings, we can isolate our application code from the .NET host process. This means that the application could be hosted by a ConsoleApp or by a WindowsService, or by some other kind of .NET process because it does not depend on it.
The dependency has been inverted by the IModule
abstraction, and all the configuration code that should execute on the main()
function of the host process will be executed as part of Bootstrapper.Run() -> IModule.Initialize()
.
With these two pieces: Dependencies Discovery and Modular Application support the AppBoot completes the Separation between Configuration and Construction from Use principle implementation. After the Bootstrapper.Run()
the application is ready to use: the DI can construct instances and the configuration has been made.
Separates the Contracts from Implementations
The Dependencies Discovery the AppBoot does, opens many design possibilities and is a powerful technique in some scenarios. For example we could separate the contracts from their implementations into different assemblies. At deploy time, we could decide which implementations we want on each deployment, and then the AppBoot will do the registrations depending on what types it finds with reflection.
Even more we could have more functional modules isolated in different assemblies. We can make these functional modules assemblies to have no references among them (as the above diagram shows). Even if they depend one on the other functionally, they don't need to have direct dependencies one on the other, and this could be enforced by not allowing references among them. So we'll not have direct dependencies between the implementations, they will only depend through abstractions which are the contracts. (The above diagram shows that the two Application Module assembly reference the Contracts assembly and the AppBoot assembly, but they don't reference each other).
This leads to a loosely coupled system and opens the possibility to change the behavior by having different deployment configurations.
This approach puts a lot of power and responsibility at deploy time. We could make some decisions that will influence the system behavior only at deploy time, without changing any code or any configurations. I will show in some future posts how we can do this, to change if some services communicate in the same process through simple function calls or inter processes through a REST API, just by doing a different deployment, without changing any code.
Facilitates Parallel Development with Multiple Teams
With these separations we could also benefit in organizing parallel development with different teams for very large projects.
If we have several modules that are developed by team A and other modules developed by team B, we could structure them in different assemblies with no references between each team's assemblies. They could only depend through the Contracts assembly, as the above diagram shows.
Once the contracts have been decided and written into the Contracts, each team can work on their implementation without the need to even load in Visual Studio the projects belonging to the other team. They just need in the \bin\Debug\
the compiled assemblies made by the other team, or they could use some fake implementations.
This may bring important flexibility in planning and monitoring the work on very large projects.
Promotes Constructor Dependency Injection
By hiding the dependency injection framework, the AppBoot controls how DI is done. In the GitHub implementation, DI is limited to constructor. Beside having consistency, this makes that the dependencies of a class are visible into the constructor. This means that we can easily:
- see classes with more than 5±2 dependencies and be critical about them
- easily stub or mock external dependencies in tests
- prevent circular dependencies (when all the classes use constructor dependency injection, most likely an error will occur if circular dependencies exist. Depending on the DI framework this may be further enforced with extensions to AppBoot)
Moreover, we could add additional constraints in the AppBoot to allow only dependencies to interfaces, promoting with this programming against interfaces practice.
Creates Patterns in the Code
AppBoot also encourages patterns in the way code is written in a project.
For example it inherits from the Unity Container the notion of having a default implementation for an interface and also more named implementations for the same interface.
This means that for the interface:
interface IApprovalService
{
bool Approve(ApproveRequest approveRequest);
}
we could have more implementations, which we declare by specifying a name with the ServiceAttribute
, like:
[Service(nameof(BannedCustomer), typeof(IApprovalService))]
class BannedCustomer : IApprovalService
{
public bool Approve(ApproveRequest approveRequest)
{
if (IsBanned(approveRequest.Customer))
return false;
return true;
}
private bool IsBanned(Customer customer)
{
// maybe check this in the DB or in a cache
}
}
[Service(nameof(PriceForCustomer), typeof(IApprovalService))]
class PriceForCustomer : IApprovalService
{
public bool Approve(ApproveRequest approveRequest)
{
// check if the order price is to high for the trust we have in this customer
return true;
}
}
At the same time we could also have the default implementation of the same interface:
[Service(typeof(IApprovalService))]
class ApprovalService : IApprovalService
{
...
}
When some other class asks in the constructor (depends on) an array of IApprovalService[]
it will get all the named implementations except the default implementation. At the same time, when another class asks (depends on) the IApprovalService
it will receive only the default implementation. The name used to declare named implementations is not used by the AppBoot. It just needs to be unique. No one could ask for a specific named implementation, it is not meant for that.
With this in mind, we could make the default implementation of the IApprovalService
as a composite of the named implementations, like:
[Service(typeof(IApprovalService))]
class CompositeApprovalService : IApprovalService
{
private readonly IApprovalService[] approvals;
public CompositeApprovalService(IApprovalService[] approvals)
{
this.approvals = approvals;
}
public bool Approve(ApproveRequest approveRequest)
{
foreach (var approval in approvals)
{
bool isOk = approval.Approve(approveRequest);
if (!isOk)
return false;
}
return true;
}
}
Now, the client code which only needs an IApprovalService
doesn't need to know that the implementation it gets (the default implementation) is the CompositeApprovalService
which depends in its turn on the named implementations of the same interface. Moreover, it doesn't need to know that named implementations of the IApprovalService
even exist.
The result resembles with the Chain of Responsibility design pattern. Separating the IApprovalService
implementation into more classes each of them dealing with another concern is a far better design than having them all in a class like:
[Service(typeof(IApprovalService))]
class ApprovalService : IApprovalService
{
public bool Approve(ApproveRequest approveRequest)
{
if (IsBanned(approveRequest.Customer))
{
// deal with banned customer
}
else if (IsOverPrice(approveRequest))
{
// deal over priced requests
}
else // add other cases
}
private bool IsBanned(Customer approveRequestCustomer)
{
// maybe check this in the DB or in a cache
}
private bool IsOverPrice(ApproveRequest approveRequest)
{
// check if the price for this order is over the limit for the customer
}
}
IDisposable
Support
When using Dependency Injection (Inversion of Control at creating object instances), there is the question on who is going to call Dispose()
on the IDisposable
objects that the DI framework has created, and when. The answer is even more tricky when the interface that the client code depends on (like IApprovalService
from above) is not inheriting the IDisposable
, but only one of its implementations is IDisposable
. So the client code doesn't even know that it got injected an IDisposable
instance.
One valid expectation is that the DI framework should dispose them, when the operation (a web request handling for example) within which they were created, ends.
Not many DI frameworks offer a good support for this. AppBoot adds this support. It offers a way to define an operation scope, it creates a child DI container for that scope, and it disposes all the IDisposable
instances which were created within that scope, when the operation ends.
In an older post Disposing Instances when Using Inversion of Control I detail the problem, the possible solutions and how this is implemented in AppBoot. The post is part of a series on dealing with disposable instances.
What Is Next?
The AppBoot is a mature library. We're using it for a while now, in production, in various projects. However, there are still a few things that could be added.
One is to have a version for .NET Core which also works with the new DI from ASP.NET Core.
Another thing would be to add support for Type Containers. At startup, AppBoot scans anyhow all the types from all the assemblies that form the application. Therefore, we could define custom attributes to decorate classes with them, and have the AppBoot initialize containers with types based on the metadata from these attributes. This kind of feature would encourage declarative programming, which would improve Separation of Concerns and reduce the complexity.
Other features would be to add support for more design patterns like Factory, Decorator, etc. These could also lead towards a better design of the client app.
My Code Design Training goes in depth on how to use the AppBoot and how to build the application infrastructure
Featured image credit: jura13 via 123RF Stock Photo
We share our insights and experiences from working on client projects and teaching developers.
Our articles blend technical expertise with real-world challenges, offering a unique perspective on Code Design.
For us, Code Design means structuring code to ensure predictability and making it easy to manage and adapt long after it's written.