Aptify has started to adopt the Unity dependency injection framework into its code base. If you are unfamiliar with DI you can think of it as a 'plug-ins everywhere' approach. Aptify already has plug-ins in specific key locations of the framework. Entities can have plug-ins for CRUD operations, record viewing, and duplicate checks. The order taking process support plugins for kit expansion, shipping and handling calculations, and pricing. But all of these features require additional development effort on our end to enable. In many formalized areas of the application (like the GE and orders), this makes sense to build out, as we envision most customers wanting to make changes there. But in other areas, this additional effort may not be worth it yet. We may not know how often customers want to configure a particular area, or we may not be ready to finalize the metadata in the database. But not having the ability to drop in different implementations would limit our ability to respond to our customer's needs. Using DI allows us to provide many more configuration points with little overhead and increases our ability to automated testing of our code.
Where can I use it
DI is mainly done with constructor injection. We provide full access to the Unity container so any type of wire up supported by Unity is possible.
We have not abstracted Unity away behind an Aptify specific interface. This means the wire up code is coupled directly Unity, and if Aptify were to change containers this code would not be portable.
The following areas of Aptify support object creation through the DI container:
- Process Components - you will find that most new process components have their dependencies injected in their constructor. Standard dependencies remain unchanged here. For example, the Process Component Config method is still how you obtain your instance of
AptifyApplication. But if you look at the documentation for the component
ShoppingCarts.CartProductValidationComponent, you will see its constructor takes a list of
ICartProductVerifiers. This component doesn't know or care which
ICartProductVerifiersit gets or how it gets them. It relies on the DI container to supply whatever implementations have been registered.
- Endpoint Security Framework - The new endpoint security framework allows security requirements of any type. You can use the DI Framework to add new requirement types.
- Services Exception Framework - The new services exception handling framework supports DI to allow new handlers to be registered to the system to handle exceptions.
- Output Mapping - When translating business logic output into the output entity object, by default the framework provides mappers for the generic entity, data tables, and POCOs. If you need support for an additional type or want to take over the output mapping process yourself, this can be done through DI.
How can I use it
Implement the interface
ISecondaryUnityInstaller from the assembly
UnityInstaller.dll and place your assembly into the bin directory. Services will automatically execute the Install method on your class during application startup. We do not support file based configuration for the object graph at this time.
When do I use it
Some areas of the framework require you to tell us about your implementations in order for them to be picked up and used by Aptify. The security framework and exception handling framework are managed this way. If you are not configuring in these areas you do not need to use DI. But you may find it useful as a means for limiting the scope of what you're changing in your system, or to increase the testability of your code. For example, if want to write a process component that calls a 3rd party service to obtain information, it would be useful to inject the object responsible for talking to the 3rd party service in the constructor. This allows you to place the component under test with a mocked 3rd party service object, and swap the mock out for the real service in production.
Do I have to register every type?
The answer is 'it depends'. In some areas of the framework (like security and exception handling), we have a common interface and the framework asks the DI container to inject all implementations of that interface. In those scenarios, you must register your implementation type or Aptify will not know about them.
But if you are building a process component and want to inject objects through the constructor, the DI container will automatically resolve everything it can. If object resolution fails you will get a very detailed error message that tells you what cannot be resolved. An example is shown below.
There are two pieces to pay attention to. The first is our Aptify error message stating we were unable to resolve the type, what it means, and that Aptify will not log about this again. The second is the stack track with the details of the type creation that failed. Here it tells you that the class
ActiveCartRetrieveComponent is failing to resolve an instance of
IActiveCartRetrieve for its parameter
Where and when does Aptify type registration happen
Our type registration code lives in assemblies that end with the name 'Installer'. For example, the assembly
ShoppingCartServicesInstaller registers types needed for shopping in services. All of our installers implement the interface
IUnityInstaller. All customer type resolution should happen in implementations of
ISecondaryUnityInstaller. The framework will execute all
IUnityInstaller implementations first, and all
ISecondaryUnityInstaller implementations second. This allows customer configurations to override the stock configuration. Installer execution is the first thing that happens during services startup. Installers that error during execution will stop services from starting.