service and context - building a context-dependent api
TRANSCRIPT
Service and contextBuilding a context-dependent API
By Michał Kurzeja
A particularly challenging project we developed included building a brand new API featuring methods to export data to partner services. That meant:
whatever was released could no longer be modified to maintain the integrity of the system a need to develop an easy way to add new features to the API doing it all without copying tons of code and in line with the logic of the applications
The answer I eventually came up with was...
...to make the services dependent on the context
Any number- or string-based value could serve as such:
API version session parameter user setting
This approach produces code as follows:
With an approach like this:
an object is produced for each class defined for a given context there is no limit to the number of contexts we can create for each service we can define dependencies for each context
With service tagging and container compiling, we can use tags to make every service we wish context-dependent. For example:
Explanation:
context.sensitive is the tag name it accepts two arguments: context and alias alias groups services that perform the same task we pick a service based on the value of the context parameter (e.g. for the processor service retrieved in the a context, the ProcessorA object is created, and for the b context – ProcessorB) the processor.universal alias returns ProcessorAB for both a and b con- text
We need a class that delivers each service when needed. Let’s call it ContextAwareContainer and start with defining its interface:
In the next few slides, we will go through an example of a class that implements the interface
Explanation:
ContextAwareContainer is a service that holds a reference to the con- tainer the inner array $services contains the IDs of services we reference through context and alias the set(...) method shown in the pre- vious slide builds the array the get(...) method shown in the pre- vious slide returns a service based on context and alias passed as arguments
Explanation:
the initializable interface prevents more services from being registe- red right after the initialization
The definition of the class as a service:
The last step is to create an object that implements the CompilerPassInterface interface. It finds the services we tagged and saves their data in the ContextAwareContainer.
Explanation:
the findTaggedServiceIds(…) method accepts an array the indices of the array are the IDs of services that have the context. sensitive tag the values of the array are the attri- butes of those tags all the data is used as arguments of the set(...) method
At this point we can already use the ContextAwareContainer to select services based on context.
Let’s further this concept by considering another scenario:
What if we had no choice but to make our service depen-dent on another service and the class of the dependency
changes with the context? For example:
To achieve this, we have to find a way to pass the alias of the dependency to the definition of the service. Let’s see
an example.
Service
Context::A Context::B
ADependency
BDependency
Explanation:
the properties attribute is a multi- dimensional array that accepts any type of value the dynamic_arguments is an array. Its keys are contexts and its valu- es are an array of aliases the new setup is as follows: when we retrieve the processor.depen- dant service with the a context, it passes a reference to a servi- ce with the processor alias and the a context (that is an object of the ProcessorA class) as its de- pendency. However, if the con- text equals b, a service of the ProcessorB class is injected into an object of the ProcessorABDependant class.
We now have to modify the ContextAwareInterface so that it stores the information on the dynamic dependencies.
Explanation:
we expand the set(…) function so that it accepts the $arguments array that contains our context- related dependencies we save the aliases in the inner $arguments array as well. now on they can be referenced with the ID of the parent service and context
Let’s have the service accept the dependencies we defined. To do so, we have to define an interface. We can call it the
DynamicArgumentsInterface.
The following is an implementation of the definition for the ProcessorABDependant class:
Now we can modify the get(…) function in the ContextA-wareContainer, so that it can pass the dependencies we
defined to a service we want to retrieve:
Explanation:
the beginning is the same – we start with retrieving our service of choice if the DynamicArgumentsInterface is implemented, we get all of our dependencies and pass them as an array to the setDynamicsArguments(...) function
if the DynamicArgumentsInterface is not implemented, the logic remains the same
The final step is to have the container compiler read all of the dynamic arguments and pass them to the ContextAwareContainer.
Let’s expand the ContextSensitiveServicesCompilerPass to reflect this.
Explanation:
to register the calling of the set(…) method, we retrieve the proper- ties option of the service that is currently being processed next, we read its dynamic_arguments parameter we finish by adding it as the last argument of the set(...) function
This is how we go about defining dependencies between services that automatically adjust to a given context.
Just a bit of effort and a few simple classes allowed us to create a functionality that expands on what can be achieved with the Dependency Injection component. The result is convenient to use and achieves a high level of abstraction.
Find the code from this article in my repo:
I hope you enjoyed the presentation. If you have any feedback, or you believe that there is a better way to solve this issue, please contact me at: