ABAP Dependency Injection – An implementation approach
Introduction
In this blog I want to show you some concepts of a solution for a “Dependency Injection”(DI)-approach which is used in one of my recent projects. It consists of a DI-Framework written in ABAP and an integration-pattern to get the framework working in a large component-based ABAP-application. The DI-framework is custom-made to fit the needs of the chosen design-approach. I will focus on the parts which helps you to organize your DI-container in a component based software design.
I won’t give you an introduction to DI but rather explain an implementation approach to fit into the abap world. If you’re looking to understand the basics of DI you should read “The issue with having many small classes” and “Dependency Injection for ABAP” or even the wikipedia page for DI would be a good start.
Getting to the requirements
In my recent project I was looking for a flexible solution of managing my OO-instances that build up my applications. While a factory pattern only describes how to design a factory for access by a client, it doesn’t describe how the factory should internally manage instances that it produces. Most of the time you will put a lot of “create object” statements into your factory to set up your application. By this your application-“set up” will be fixed.
Also if you follow the SOLID-Principals in your software-design you will soon face problems described in the blog “The issue with having many small classes”. You have a lot of small classes and you have to write code for instance-creation and management in your factory-classes. This could become annoying.
Another drawback is that it’s hard to reuse your factory since your application-“set up” is fixed and you can’t replace certain classes for special purposes.
So the solution is to combine the factory pattern with a DI-framework. The DI-framework handles the instance management and the factory-pattern provides a way to access certain instances. The benfit is, that you keep the framework-code inside the factory and out of your business code. Someone who uses your factory doesn’t even have to know about the DI-framework.
So in the end the framework fulfills the following reqirements
- Quick/easy to set up
- Debugging possibilities
- Support for all DDIC-Datatypes
- No dependencies in business code (i.e. not forcing to inherit some class)
- Class creation (with “create object” or factory methods)
- Class configuration (constructor-based or with setter-methods)
- Modularization to match a component based architecture
- Support for “where-used list”
- Replacing classes in a given setup
Quick Example
I will first start to give you a quick introduction into the DI-framework and how the basic usage is.
The following class-diagram shows the setup for the examples (“zif_*” for interfaces; “zcl_*” for classes).
“zcl_service_a” needs some other classes to get its job done. “zcl_persistence_a” implements some kind of database access and “zcl_calulate_a” provides some kind of calculation.
If you want to use “zif_service_a” you first have to instantiate “Persistence A” and “Calculate A”. Then you can create “Service A”.
The ABAP code would look like this (In ABAP NW740 syntax)
DATA(lo_persistence) = NEW lcl_persistence_a( ).
DATA(lo_calculate) = NEW lcl_calculate_a( ).
DATA(lo_service) = NEW lcl_service_a(
io_pers = lo_persistence
io_calc = lo_calculate ).
This is quite simple. You could put that into your factory and you would be fine. But if your component grows and involves more and more classes the task of putting them together gets annoying. Especially if you tend to create small classes.
On the other hand it gets difficult to reuse your setup (the way you put your classes together). If you want to use your service in a slightly different context and want to replace i.e. the calculate-implementation by your own you have to copy the whole factory and replace the line where you create the calculate-instance. Some nicer approach would be to say: “do everything as is, but instead of your calculate implementation take mine”.
With the DI framework you would do something like this. In the first step you would create the DI-container:
" Create DI-Container
DATA(lo_container) = zcl_di_container=>create_instance_default( ).
Then you would register all your classes in the container, so the container has a bulk of classes to work on.
" Register classes into container
lo_container->register_classname( iv_classname = 'ZCL_SERVICE_A' ).
lo_container->register_classname( iv_classname = 'ZCL_PERSISTENCE_A' ).
lo_container->register_classname( iv_classname = 'ZCL_CALCULATE_A' ).
Now you can query the container to get an instance of your “Service A”:
DATA lo_service TYPE REF TO zif_service_a.
lo_container->get_instance_value(
CHANGING cv_target_value = lo_service ).
" Use your instance
lo_service->do_something( ... ).
The container analyzes the type of the variable “lo_service”. Since it is “zif_service_a” the container looks for a registered class that matches this interface. This would be “zcl_service_a”. It analyzes the constructor of this class and looks for classes that match the type of the specific parameters. So it will first create instances of “zcl_persistence_a” and “zcl_calculate_a” before it creates an instance of “zcl_service_a”. The instance will be copied into the variable “lo_service”.
The construction and analyzing process is done recursively and it can detect possible inifinite recursions. You can even use other datatypes on your parameters like int, string, structure or tables, which also have to be registered in the container.
Replacing classes
The code above would be placed into some kind of factory. While the registration code would be placed into the constructor, the querying code would be placed into the factory methods.
CLASS zcl_factory_a IMPLEMENTATION.
METHOD constructor.
ao_container = zcl_di_container=>create_instance_default( ).
ao_container->register_classname( iv_classname = 'ZCL_SERVICE_A' ).
ao_container->register_classname( iv_classname = 'ZCL_PERSISTENCE_A' ).
ao_container->register_classname( iv_classname = 'ZCL_CALCULATE_A' ).
ENDMETHOD.
METHOD get_service_a.
" ** RETURNING VALUE(ro_service_a) TYPE REF TO zif_service_a.
ao_container->get_instance_value( CHANGING cv_target_value = ro_service_a ).
ENDMETHOD.
ENDCLASS.
If you want to replace a specific class you can do it by inheriting the factory-class and register your own implementation. I.e. if you want to replace the implementation “ZCL_SERVICE_A” of interface “ZIF_SERVICE_A” by your own implementation “ZCL_SERVICE_A_BETTER” you can do this:
CLASS lcl_factory_a_better IMPLEMENTATION. " INHERITING FROM zcl_factory_a
METHOD constructor.
super->constructor( ).
ao_container->register_classname( i_var_classname = 'ZCL_SERVICE_A_BETTER' ).
ENDMETHOD.
ENDCLASS.
The container acts like a stack and every new registered datatype or class would be put on top of this stack. If you query for a specific type the container checks for matches from top to bottom. The first match will be returned and used. Since your own class ZCL_SERVICE_A_BETTER is registered last it is on top of the stack. So it will be the first matching type for the interface “zif_service_a” and will be selected. By this way you can replace specific classes.
Debugging
If your container contains a large amount of classes you may want to check if instances are bound to the correct parameters. The di-framework provides a possibility to trace the querying of the container. The trace is rendered into a graph. The following picture shows the graph from the small example above:
You can see that both parameters of the constructor from “zcl_service_a” are bound to the correct classes.
Namespaces
Another concept realized in this DI-framework are namespaces. In an application that consists of different components each component would need its own container. That’s because you can’t put every class of every component into a single container. This would result in conflicts between different components which implement shared interfaces.
Another option is to use namespaces. Each component uses the same container-data-core but uses a different namespace within this container. The namespaces are isolated from each other so that conflicts are avoided. If you want to use a class of another namespace you can set an alias to the other namespace.
The following example shows the concept of namespaces and aliases.The following class-diagram shows “Service B”:
You can see that “Service B” uses “Service A”. Furthermore it implements “zif_calculate” which is also implemented by “Service A”. The code to set up the DI-container for this scenario would be the following:
DATA(lo_ctx_data) = NEW zcl_di_context_data( ).
The instance of “lo_ctx_data” is the core of each container. It contains the registered classes and datatypes. If you have different container-instances which share this instance of “lo_ctx_data” they will all act on the same container data.
If you create a container you can set up a default namespace. If you register classes they will all be placed into the containers default namespace. “Service A” would be set up in the following way:
DATA(lo_container_a) = /abk/cl_di_container=>create_instance_default(
i_var_namespace = 'urn:a'
i_obj_context_data = lo_ctx_data ).
" Register classes into container
lo_container_a->register_classname( iv_classname = 'ZCL_SERVICE_A' ).
lo_container_a->register_classname( iv_classname = 'ZCL_PERSISTENCE_A' ).
lo_container_a->register_classname( iv_classname = 'ZCL_CALCULATE_A' ).
“Service B” is set up in the following way. It will reuse the previously created instance of “lo_ctx_data”:
DATA(lo_container_b) = /abk/cl_di_container=>create_instance_default(
i_var_namespace = 'urn:b'
i_obj_context_data = lo_ctx_data ).
" Register classes into container
lo_container_b->register_classname( iv_classname = 'ZCL_SERVICE_B' ).
lo_container_b->register_classname( iv_classname = 'ZCL_PERSISTENCE_B' ).
lo_container_b->register_classname( iv_classname = 'ZCL_CALCULATE_B' ).
lo_container_b->register_alias(
iv_typename = 'ZIF_SERVICE_A'
iv_query_namespace = 'urn:a' ).
If you retrieve “Service B” …
DATA lo_service TYPE REF TO zif_service_b.
lo_container_b->get_instance_value( CHANGING cv_target_value = lo_service ).
… you will get an instance constructed like in the following picture:
(Click for a larger image)
You can see two namespaces. “urn:a” for “Service A” and “urn:b” for “Service B”. “Service B” has a registered “alias” which connects the two namespaces on the type “zif_service_a”. So just the type “zif_service_a” of namespace “urn:a” is visible in namespace “urn:b”. So you can see that the namespaces are clearly isolated to each other and the querying and construction is restricted to its namespace unless you set an explicit alias.
DI-Modules
The last concept I want to show are modules. Modules are a descriptive way to set up a container. You can describe five different aspects with a module:
- Module-Name
- Default-Namespace
- Import dependent modules
- Public module items
- Container registration
The following code shows an example for a module for “Service A”:
CLASS zcl_module_a IMPLEMENTATION.
METHOD zif_di_factory_module~get_modulename.
rv_module_name = 'Service A'.
ENDMETHOD.
METHOD zif_di_factory_module~get_default_namespace.
rv_namespace = 'urn:a'.
ENDMETHOD.
METHOD zif_di_factory_module~register_imports.
" nothing
ENDMETHOD.
METHOD zif_di_factory_module~register_interface.
io_container_mng->register_alias(
iv_typename = 'zif_service_a'
iv_query_namespace = 'urn:a' ).
ENDMETHOD.
METHOD zif_di_factory_module~register_classes.
io_container_mng->register_classname( iv_classname = 'ZCL_SERVICE_A' ).
io_container_mng->register_classname( iv_classname = 'ZCL_PERSISTENCE_A' ).
io_container_mng->register_classname( iv_classname = 'ZCL_CALCULATE_A' ).
ENDMETHOD.
ENDCLASS.
Modules can hold several namespaces. But there is always one namespace that acts as default namespace. Every class or datatype is registered with the default-namespace unless a namespace is explicitly provided on registration.
A module can build a relationship to another module in order to get access to public services (registered classes) provided by the other module. This is done in the “register_import”-Method. I.e. in the previous section “Service B” imports a service from “Service A”. In terms of modules this would be setup by a relationship between these modules. So “Service B” would import module “Service A”. In the module declaration of “Service B” you would find this method implementation:
METHOD zif_di_factory_module~register_imports.
io_registry_imports->register_factory_module( NEW zcl_module_a( ) ).
ENDMETHOD.
The method “register_interface” describes which registered types of the module should be public and imported into another namespace once the module is imported elsewhere. The method receives the DI-container of the foreign module and can register aliases into the foreign container. The aliases should point to the modules own namespace. By this namespaces of two modules get connected and dedicated registered types are accessible in the foreign container. So this method describes some sort of public interface of the module.
The final method “register_classes” fills up the own default-namespace with classes and datatypes. Every class and datatype has to be registered there, which should be considered on construction of instances by the DI-framework for this module. Into this method you can also place factory classes which are not DI based. So you don’t have to use the feature of importing modules to get access to instances of another components.
If you want to use a module you can do it with this line:
DATA(lo_container) = zcl_di_factory_generic=>get_instance( io_init_module = NEW zcl_module_b( ) )->get_di_container( ).
Now you can retrieve your instance like shown in the previous examples:
DATA lo_service TYPE REF TO zif_service_b.
lo_container->get_instance_value( CHANGING cv_target_value = lo_service ).
The benefits of using modules are that …
- you have a managed way how different components are connected to each other.
- you don’t have to care about creating and managing the DI-container.
- every module is only imported once. So if n modules import the same module its content is not registered n times but just once.
- since the module-class is used in a descriptive way, you can dynamicly replace modules.
Finally the DI-implementation provides a code generator which makes module creation a bit easier. So there is a configuration for the SAP “Service Implementation Workbench” (SIW) that can create the module-code and wraps it into a nice factory-class to provide a type-safe access to the DI-container and hiding the DI aspects from users. The SIW generates your factory and module-code and you just have to fill the five module-methods. In the end you have a nice working DI-based factory-class.
Finally
This blog could not handle every aspect of this DI-framework. But I hope I could show you the intended design approach to fit in a large component based application.
Please let me know what you think!
Edit (6.1.2013): Some parts in section DI-Modules
Hi, very interesting approach to some enterprise needs.
The SIW-Configuration-approach sounds tempting, any chance to elaborate on that?
Any plans to release the code?
Cheers, Kai
Unit testing is one of the most important things in ABAP world, and anything that can make it easier is to be welcomed.
Are you aware of the following open source ABAP project started by Uwe Kunath?
http://uwekunath.wordpress.com/2013/10/16/mocka-released-a-new-abap-mocking-framework/
Do you feel you are both trying to achieve the same thing?
Often many different people are working seperately on the same (or a very similar) problem, and several of them post their solutions on SCN. One good example is the various people trying to write their own versions of ABAP2XLS from scratch and posting the results on SCN.
Great post! Thanks, and I'm looking forward for the the release of the framework 😉
@Paul: I feel that DI and Mocking are not the same. I would say that Mocking is one use case for DI, but there are definitely more that this one. But nevertheless, Mocka is great and should be understood and used by every serious ABAP developer.
would second that, mocka dynamically generates testable stubs for you, whereas this article concentrates on particular enterprise-approaches in their Dependency Injection framework.
Thanks 🙂
Would be happy to release this framework, but although it is not developed during a customer project (just used), i am not sure about the legal part...
I had the wrong end of the stick. I've read the blog again, and I'm going to read it again and again until I really understand it. Horst Keller from SAP said that OO programming was easier to understand than procedural programming, but I don't think he meant things like this.
I had only ever heard dependency injection talked about before in terms of unit testing i.e. passing mock classes into constructors as opposed to real classes during unit tests. That was what was confusing me.
This seems to be about when you have a real class where you need to pass in instances of a large number of other classes in the constructor. Please correct me if I am wrong.
Initially I thought that maybe now we were talking about using less lines of code to insantiate the correct object, but that doesn't look to be the case as you still have to register all the small class names.
Are we talking about isolating the actual types (subclasses) of the instances being passed in to the main class, to some sort of configuration table or something? This makes the code easier to change in the future?
Or have I totally missed the point?
Martin Fowler coined the term "dependency injection": Inversion of Control Containers and the Dependency Injection pattern
You're right on the track, usually you register classes for types/interfaces and the DI-Container is responsible for creating instances of requested types (interfaces or classes).
Creating instances involves (recursively) resolving the dependencies of the class being instanciated (think of each constructor parameter being the class' dependency), so the DI provides(injects) the dependencies into the objects being constructed.
This leads to a design where each class explicitly specifies its dependencies and no class (expceptions being factories etc.) creates isolated object-instances by itself. Since every class now explicitly specifies its dependencies you can supply these dependencies from the outside and this enables you to switch from the database-class to a unit-testable-class or some mocka-generated class. Therefore it is often mentioned with unit testing.
There are two main design reasons i was going to setup my factories with DI (beside the fun of programming ;):
The first one is that i was looking for a way to build factories which have a non-fixed configuration of my instances. I.e. we had a software delivered to a customer where we had factories which created their instances by a lot of "create object" statements. For normal use everything was fine and the configuration of instances matched the costumer needs. But they wanted to build a report for special purpose (data migration). For this they wanted to replace certain classes. Although we had a lot of BAdIs we couldn't predict every upcoming need. So in the end they had to copy the hole factories and replace some of the "create object"-statements. But they had duplicated 95% of factory code to replace 5% code.
The second reason is that i think "unit tests" are a success factor in development (like already mentioned). To get into "unit tests" you must respect good oo design and should follow the SOLID-principals to have a reasonable size and cut to successfully apply a unit test to your class. I still see a lot of monster classes (many methods/large methods) and "function group"-like classes which make it difficult to write a maintanable "unit test". If you're going for smaller classes, you have to put more effort in the part of instance creation and management. I hope that this framework could assist in this task.
Oh, and your idea of putting classes into a configuration table to fill up the di-container is nice. I already thought about that, but in the first step i was looking for a solution to keep the "where-used" list working and to have a working default-configuration of my application that is independant of configuration table entries.
But the framework offers a solution that you can replace certain classes of a such a default configuration (di-module) by implementing a BAdI. The BAdI-Implementation could read a configuration table to add/replace classes in the di-container.
Hi,
I like this explanation of DI.
James Shore: Dependency Injection Demystified
What I like about that article is not the article itself but the one it points to which is described as over-complicated and creating far too many classes.
In that article you get an example of dependency injection in the real world, nothing to do with software. The real world, the one we live in. That is the sort of example I like - a la this is what happens in real life, this is why such and such is GOOD in real life, now we can think about trying to do the same thing in the alternate reality which is writing computer programs.
I've re-read the Martin Fowler article, and looked at the all the links mentioned in your blog, and then looked at the James Shore article.
I am going to do an example myself and publish it to see if I have got my head round this. I can't try to explain it to someone else (i.e. a blog) unless I understand it myself, at least that's the idea.
Up till now I thought dependency injection was inserting instances of classes into a constructor. Then when you tested the class with that constructor you ijected instances of mock/stub classes. That has been working fine for me.
If you are lucky enough to have a bi bunch of re-usable Z classes, and you have a new application that can use 10 of them as-is, and 5 of them with small changes that can be achieved by a sub class, than dependency injection sounds just the job.
What I don't yet understand is instead of having a factory or "assembler" method just doing a big bunch of CREATE OBJECTS one after the other and then firing them of into the constructor of the real class, or firing the stubs into the real class during the SETUP in the unit test, what's being advocated is having some sort of "container" class sitting in the middle.
So what seems to be proposed is instead of, for each small class, instead of writing (un the factory / assmebling method:-
DATA lo_small_class TYPE REF TO zif_something.
CREATE OBJECT lo_small_class TYPE zcl_something.
CREATE OBJECT lo_application_class
IMPORTING io_small_class = lo_small_class.
The idea is to write:-
DATA: lo_magic_container TYPE REF TO zif_magic_container.
lo_magic_container = zcl_magic_container=>get_instance( ).
lo_magic_container->register( io_contract = 'ZIF_SOMETHING'
io_actual_class = 'ZCL_SOMETHING' ).
lo_magic_container->assemble(
CHANGING co_generic_class = lo_application_class ).
Thus far I can see how the second approach could work technically, what I have not figured out is why it is better. It looks like just the same, or more, code, is more complicated and looks more like black magic.
Both approaches seem to satisfy the main thing Martin Fowler was looking for i.e. separating the configuration/wiring together of different concrete classes from the application code, and "inverting" the dependencies so the application (client) does not have control of what exact concrete types it gets.
Maybe as I read more and experiment more I will have a light bulb moment. It is a fascinating subject.
Cheersy Cheers
Paul
Hi Paul, you're right on the point. Both ways are perfectly acceptable and the mechanism/technique how to create those dependencies/instances does not constitute whether you actually use "dependency injection".
However, creating those instances at application-bootstrapping is very tedious and not really simple or easily readable if you have a normal object graph/tree where you have to create sub-sub-objects for a sub-object, that you then inject in your actual object. you get the idea: providing dependencies for an inherently complex and recursive object graph, possibly having to manage singleton-instances, calling factories etc.
So you simply want to tell the container what you've got (and perhaps what to bind to which interface/type) and the container's job is to figure out what to use to create the requested instance and automatically resolve any recursive dependencies that normally exist and do mess up your dependency-definition...
This looks very promising because when getting into unit testing and dependency injection, I quickly found that instantiating classes becomes cumbersome.
For now I'm sticking to factories though because there is no standard DI-framework implementation for ABAP and also, I'm mostly introducing DI and unit tests into existing apps that can't easily be bootstrapped. Keep up the good work anyway!
http://scn.sap.com/community/abap/blog/2014/03/25/shoot-me-up-abap
The blog was my two pence worth of contribution to the subject. of ABAP injection.
You will notice in the amounts a discussion about the open source framework MOCKA for easily creating mock objects, and in the 750 release of ABAP there is indeed a standard way of doing this....
Cheersy Cheers
Paul