In the first part of this series I introduced a file backend which supports multiple types of storage types and allows the combination with authorization check or file filter classes.
The design pattern in use is the decorator pattern. Every class which implements additional functionality declares the usage of a member instance which is described by the same interface which is already implemented by the class itself.
This allows us to have more flexibility than in another approach which used inheritance.
Having a decorator based file access backend and a factory which creates and combines these instances for you comes close to what the requirements said. But already the very first requirement – “Multiple storages will need to be supported by configuration, e.g. cluster database storage or SAP Generic Objects Services based storage” is not yet satisfied: There is no possibility to configure the object hierarchy yet.
In this blog post I’m going to show you how this can be achieved by utilizing an IoC Container.
Configuring the IoC Container by API
The decorator pattern allows us to have the flexibility we need. The instances may be combined without any restriction since they implement the same interface. But this approach is still a static one, because of the way the objects are created.
If this coding was part of a factory, you would need to have a new method every time you request a slightly new combination of object instances.
In software which is to be shipped to customers, having a factory which is not extensible would be too restrictive.
The coding which creates and combines all the instances could also be implemented by a BADI. The advantage would be that every customer could implement its own file backend by combining certain instances.
Unfortunately, in the first part of this series, one requirement stated clearly that there could be multiple scenarios in which the file access could be used: “within the same system, there could be multiple scenarios, which require different kinds of storage types”
So what would be the solution? Have a BADI implementation for each of these scenarios?
With an IoC Container you could define freely combined object hierarchies. This definition could be done in source code but could also be done in customizing.
The basis of the configuration of an IoC Container is the contract. The contract is, technically spoken, an ABAP OO Interface.
There could be one or multiple implementers for each contract. Implementers are, technically spoken, ABAP OO classes which implement this interface.
The easiest example which shows the usage of an IoC Container is the creation of one class for one interface:
The IoC Container is created using the following call:
DATA lo_ioc_container TYPE REF TO /leos/if_ioc_container.
* create IoC container
lo_ioc_container = /leos/cl_ioc_container=>create_empty_instance( ).
Next, a contract and its implementer need to be registered:
* register instance for binary access
iv_contract = '/leos/if_ioc_demo_bin_access'
iv_implementer = '/leos/cl_ioc_demo_bin_access' ).
The instance can be resolved with the GET_IMPLEMENTER-method of the IoC Container.
This example does not have any benefit yet. The implementation is not yet defined in the customizing and we didn’t even combine two instances with each other. The combination comes next.
If there are multiple implementers for one contract, they can be distinguished by using a filter value.
This filter value can be handed over when the GET_IMPLEMENTER-Method is called.
Since we need to combine two instances directly with each other, we use another basic concept of the IoC Container: Configuration of Constructor Injection. In the example the internal table LT_CI takes care of the constructor injection
Requesting the implementer still works the same way we’ve already seen before.
The whole code looks like this:
The debugger shows how this object is nicely nested:
The IoC Container will resolve all dependencies, which are declared by constructor injection, automatically for you.
If the implementer of the dependency declares its own dependencies, they are of course also resolved before the object is getting created – which means that you can nest dependencies as deep as you want.
Features like lifetime control, Setter-Injection, passing parameters by the application or passing default values to a constructor are also included.
Up to this point, there was still a drawback: We defined the implementers directly in the coding at design time. Manually nesting these implementations would still be easier. So let’s shift all the setup routines of the previously shown coding to the customizing.
The whole definition starts with an application. It’s up to you which name you chose.
Now let’s declare the contracts. There is only one in this application. Since we want the IoC Container to create a new instance every time we ask for it, we set the lifetime to “transient”
There are two implementers yet, one is the default implementer, while the other one is only valid if a filter value ‘BA’ has been passed by the application or has been declared in the constructor injection configuration.
The constructor injection configuration of /LEOS/CL_IOC_DEMO_ACCESS_AUTH comes next:
That’s it – we are done.
The whole coding to get an instance can be simplified to this piece of code:
The setup of complex object hierarchies can be dramatically simplified with the help of an IoC Container. The container takes care of the creation of the objects you need, satisfies all required dependencies and controls the lifetimes. Changes to the hierarchy are possible by only changing the customizing. The code is never again touched for this type of change.