Description
Through implementing the server module, I got the chance to experience the API from the core
module and the protocol
module, and I have a few reflections.
Project structure
The core
module and protocol
module, for most of the time, are going to be referenced and used together for any useful APIs. The separation of these two modules are a bit unhandy. It would probably introduce more headache when their module versions digress.
SPI
The idea of SPI is nice. It allows users to use their own implementations to plugin functions required by the API. However, through the implementation of the server module, I feel that a good SPI design is quite hard. There are always some edge cases where a standard SPI interface won't be able to cover. For instance, when designing the http.Request
SPI, specifically the PathParam
method, I am really assuming the usage pattern of the router library I had in mind. Such a SPI will probably introduce more problems down the road.
Event callbacks
The event callback mechanism in the service implementations are of similar defect. This mechanism are supposed to provide a hook for developers for more customized processing logic at specific events. For instance, if developers decide on the architecture where the Group resource and the User.groups
attribute will achieve eventual consistency, then the callback will be a good place to submit a sync job to a middleware after the Group resource has been changed/persisted. However, this has two problems.
- One is that the implementations must not be blocking, albeit the mechanism really had no way to enforcing it.
- Another is that I am not sure how should any errors be treated. Should the error be returned to caller, or should it be ignored since persistence was already successful?
Resource event propagation
The propagation mechanism achieves its goal, but the design is troublesome. We are forced to maintain a two way reference in the resource structure (i.e. two way between parent and child property), and there is no way to NOT react to an event once the subscribers are in place.
Proposed solution
In this refactoring effort, we propose the following solutions to the above issues:
The pkg module and change of entry point
Merge the majority of core
module and protocol
module to a pkg
module. The top level entry point would be services instead of handlers. The services can be directly used by user implementations of handlers, which can choose whatever library the user sees fit. The pkg
module would be independently developed and versioned.
This also addresses the SPI issue. Now, since users build their own handlers, they are no longer restricted by the SPI. Http router libs and logging libs can be used directly without having to wrap it in a SPI implementation.
Services exposed as interfaces
SCIM services will be exposed as interfaces instead of plain structures. This allows users to create facade implementations that wraps them, and provide additional logic based on the results from the wrapped service.
The users can have full control whether the logic will be blocking or non-blocking, and can decide what to do with any errors.
Enhanced navigators
Because navigators actually maintain the stack from the root of the resource down to the property of interest, it is the perfect place to implement the event mechanism. Navigators will expose Add
, Replace
and Delete
methods which delegates to those of the Property
, but perform event notification along the stack afterwards.
When an operation does not wish to cause event propagation. It can simply use nav.Current().Add()
instead of nav.Add()
, for instance.
The resulting benefit is a simpler resource structure. Properties no longer have to maintain a reference to their container.
Other improvements
- Parameterized annotations: annotations accepts parameters. Subscribers and Filters can use these parameters to customize their behaviour. This would reduce the number of configurations and annotations needed.
- Use Go 1.13 error wrapping feature. Use
errors.Unwrap
to determine the root cause
- Address #37 by introducing
ref
parameter in DB.Replace
method.
- Largely simply the complexity of a synchronizing visitor, used by filters. Implement a navigator which pushes
OutOfSync
marker property onto the stack when going out of sync, freeing the visitor of the complexity of maintaining both stacks and sync status.