Saman
Saman is a lightweight library for data processing like conversion, persisting or synchronizing.
Albizia Saman, originally Samanea Saman and called 'árbol de la lluvia' ('The Rain Tree') by locals is a plant originated in middle and south america. Besides its characteristic to close its leaves when its raining, the Albizia Saman is also the plant that is suspected to be able to convert more CO2 into its structure than any other plant on the world, nearly twice as much as the second contestant that is bamboo.
Anonymous data processing
The base principle of Saman is the same as in Spring's ConversionService: provide a generic entry point for every consumer in need for data processing, but hide the component that actually processes the data.
In Saman, this entry point is the com.mantledillusion.data.saman.ProcessingService interface, with com.mantledillusion.data.saman.DefaultProcessingService being the reference implementation.
The ProcessingService implementations are expected to hold a set of com.mantledillusion.data.saman.ProcessingService.Processor implementations. The Processor<SourceType, TargetType> is a generic interface for processing data from SourceType to TargetType.
The class com.mantledillusion.data.saman.ProcessorRegistry is able to build a matrix of SourceType → TargetType mappings out of a set of Processors, from which it is then able to provide a matching Processor implementation for every specific SourceType/TargetType combination. With this feature, ProcessorRegistry can be used by ProcessingService implementations to locate the correct Processor for a processing; DefaultProcessingService is implemented that way.
The package com.mantledillusion.data.saman.interfaces contains several Processor extending interfaces for the most common use cases.
Using the ProcessorRegistry containing the matrix of Processor implementations, The ProcessingService is able to provide generic processing to consumers over its process*() methods. The setup has several benefits over simply implementing a non-generic set of converters and calling them individually:
- One reference to the ProcessingService is enough for every consumer instead of referencing and calling multiple components directly.
- The Processor actually performing the processing is unknown for the consumer of the ProcessingService; as a result, code erosion like "historical growth" is prevented at its origin.
- Processor implementations are reused automatically if the ProcessingService is called for the same SourceType/TargetType combination from different parts of the code.
- Processing code is standardized automatically as it has to conform to the Processor interface (or one of its extensions).
Processor Hopping
The regular data type for Processors are self implemented POJOs which, in most cases, contain sub objects that require processing to. For example, PojoA might contain a field of PojoB, which itself contains some primitive type fields like int or char.
In Saman, Processors are not only called with the source object to process, but also with the instance of ProcessingService performing the processing. As a result, a Processor for PojoA might process that type's primitive fields, but will call the ProcessingService to find a Processor for the processing of PojoB.
This approach has multple benefits:
- "Separation of Concerns" is enforced inheritly for processing code.
- More code is reused, as Processors can be triggered by consumers and by other Processors as well.
- Processor code becomes much cleaner.
Process Contexting
The "Separation of Concerns" approach of Saman works great and has a lot of benefits, but it causes a Processor not knowing the context in which it is triggered. In most cases this is not necessary or even favorable, as it preserves the code's simplicity and enhances re-usability.
But occasionally, there are situations in which it is necessary for a Processor to know the context it is operating in. Imagine a ProcessingService containing 2 Synchonizers for the types DatabaseEntityA and DatabaseEntityB, where A contains a List of B. The Processor for B might be interested in the instance of A its instance of B belongs to; for example, the validation of B differs in relation to how A's fields are set. Because of "Separation of Concerns", if B does not contain its A instance, there is no way a Processor could get that instance of A.
This is where process contexting steps in. The ProcessingDelegate given to Processors is not only able to perform sub-instance processing; it also holds a map of objects that defines the context the processing takes place in.
So in the scenario of above, the Processor of A could add the A instance to the context, so the Processor of B is able to access it again.
Note that contexts are redefined on every hop between processors, so it is impossible for processors to access or overwrite context data of another processing chains.