Open minborg opened 4 years ago
I suggest using more standard term DTO (data transfer object) as opposed to "data carrier" - it also then suggests a standard term for "Managers" - DAO (data access object).
This is a major change, suitable for when we re-write for Java 17.
Problem Description
There are several drawbacks with the current solution where data carriers are responsible for their own management:
Loosely speaking, Marshallable resembles "active records" which have several drawbacks that are generally well-known within the community. Consequently, I will not elaborate more on that here.
Classes that are to benefit from
SelfDescribingMarshallable
must sacrifice its one and only superclass.Classes that sub-classes
SelfDescribingMarshallable
will inherit and carry the API commitment ofSelfDescribingMarshallable
and because data carriers are often public, the total API surface will increase unnecessarily and sometimes dramatically.Classes sub-classing
SelfDescribingMarshallable
will carry the following methods above its bona fide data carrier methods and therefore burdens the user when coding as shown hereunder:Since Java 14, records can be used and are expected to replace and simplify the use of opaque data carriers.
Many organizations have existing data carrier classes they want to reuse without having to modify them.
The current implementation of
SelfDescribingMarshallable
relies on reflection requiring the class to be publicly open to deep reflection under JPMS.The current solution does not allow the use of immutable data carriers. Immutable classes are robust, fail-fast, inherently thread-safe, and can benefit from a rich set of compiler optimizations. Configuration objects, for example, are best implemented as immutables.
The current solution does not allow different implementations of a class to be provided depending on the data carrier content. For example
Point(0, 0)
could not be implemented byOrigoPoint()
in the current solution.The handling of data carriers is not pluggable. Once a concrete implementation is picked, it will always be used regardless of capabilities potentially added later.
Methods invoked on the
Marshallable
typically require an initial "get" operation used to obtain, for example, a serializer, for the class in question. Many of these methods are often invoked billions if not trillions of times during the application's lifetime.Proposed Solution
Therefore, I propose that we decouple the data carrier from the logic that creates and accesses the data carrier. Thus, creating single responsibilities for these types of classes:
Manager Example
Here is an outline of a Manager skeleton:
Methods could be grouped by means of methods like
marshaller()
that exposes all the marshaling methods or by creating distinct sub-interfaces that hold related methods together (e.g.Marshaller<T>
).User code Examples
Legacy classes
Modern Java
Creating Managers
Manager implementations can be created in many ways. For example, Managers could be created using reflection (as shown above), using builders or they can be generated.
Migration
Once implemented, the end-user code could be migrated at that time, later or never. Thus, older code will continue to run unaffected.