I am trying to implement a new architectural approach for inter-layer communication by using abstract models and decorators.
Conventionally, when we design the layers of a monolithic application, we would have a application(controller) layer, a domain(business) layer and an infrastructure(persistence) layer. Those layers communicates with each other by a concrete model definition, that when being read from a database, navigates through a repository, then to a service and finally to a controller that dispatches it out to a client.
That said, let's get to the point...
Normally the infrastructure model must not be the same as the business model which also must not be the same as the UI model. The infrastructure model may concern only with it's destination repository strucutre, the domain layer may concern only with business operations, and the UI layer must only be concerned with it's clients and data read/input.
All those layers must "understand" an abstraction of each other, or even implement bi-directional value mapping(or DTOs), in order to communicate between them. Mapping/DTO is a bad approach because we would have unnecessary processing for data-mapping.
So, here is what I'm trying to do: Separate layers by using decorators for communication.
In that approach, we would have abstract components and it's decorators on a shared module, and each layer would have it's own decorator.
Eg.:
With that approach we could:
Here's an example on paper:
Sketch
So...what I ask is for suggestion, tips and some opinions if it's a good approach.
[UPDATE #1]:
To simplify my question, I will post a use case below:
Shared Components:
// Abstract Foo
public abstract class AbstractFoo {
protected Long id;
protected String color;
protected LocalDateTime lastModified;
protected Long getId();
protected String getColor();
protected void setId();
protected void setColor();
}
// Abstract Decorator(wraps foo)
public abstract class FooDecorator extends AbstractFoo {
private final Foo foo;
protected FooDecorator(Foo foo) { this.foo = foo; }
protected Long getId() {return foo.getId();}
protected String getColor() {return foo.getColor();}
protected LocalDateTime getLastModified() {return foo.getLastModified();}
protected void setId(Long id){foo.setId(id);}
protected void setColor(String color){foo.setColor(color);}
protected void setLastModified(LocalDateTime lastModified){foo.setLastModified(lastModified);}
}
Infrastructure Layer:
// Defines the database model for Foo
// Only concerned about the table structure
@Entity
@Table(name="TBL_FOO")
public class FooEntity extends FooDecorator {
public FooEntity(Foo foo) {
super(foo);
}
@Id @AutoGenerated @Column(name="ID_FOO")
protected Long getId() {
return super.getId();
}
@Column(name="DS_COLOR", length="255")
protected String getColor(){
return super.getColor();
}
@Temporal @Column(name="DT_MODIFIED")
protected LocalDateTime getLastModified(){
return super.getLastModified();
}
}
Domain(business) Layer
public class FooBar extends FooEntity {
public FooBar(Foo foo) {
super(foo);
}
//let's open the ID for the outside world
public Long getId() {
return super.getId();
}
// Paint with a red color
public void paintAs(Color color) {
super.setColor(color.getKey());
}
// Upper level may want to know the current color
public Color getColor() {
return Color.parse(super.getColor());
}
public boolean isModifiedSince(LocalDateTime compare) {
return DateUtils.compareMillis(super.getLastModified(), compare) > 0;
}
public LocalDateTime getLastModified() {
return super.getLastModified();
}
}
Application(view) Layer
/**
* JSON Eg.:
* {fooBarId: 1, fooBarColor: 'RED'}
*/
public class FooBarView extends FooBar {
public FooBarView(Foo foo) {
super(foo);
}
// Maps field to JSON as 'fooBarId'
@JsonMap("fooBarId");
public Long getId() {
return super.getId();
}
// Maps field to JSON as 'fooBarColor'
@JsonMap("fooBarColor")
public String getColor() {
return super.getColor().toString();
}
}
-
// Pseudo Code
public class FooBarREST {
// '/api/v1/foobar/{id}'
public getFooBar(Long id) {
return new FooBarView(find(id));
}
}
So...what I ask is for suggestion, tips and some opinions if it's a good approach.
I'm skeptical. Call it two 9s.
Architecturally, your sketch suggests that these isolated components are peers, which I don't think is a practical way of modeling the concerns at all. Domain is hugely important to the business. Persistence, not so much (if we had enough memory, and could keep our servers running, we wouldn't bother).
Furthermore, there are design choices that bleed into each other; if your choice of persistence is storing event histories, then your domain model and your repositories have to agree on that, and the contracts between those two components should be explicit about that -- yet none of your other components care how the state of a thing is implemented, they just need some query surface.
And maybe not even that - the application responds to queries from the client with representations, not objects; if those representations are cached ahead of time (CQRS) then it won't care about the objects in the domain model at all.
On top of that, I think the architecture as drawn trivializes the real complexity of the dependencies. Part of the point of recognizing that these are different components with different concerns is so that you can swap them out for one another. Every api change shouldn't be a rebuild-the-world event (think semantic versioning).
Clarifications added after sample code added
// Abstract Foo
public abstract class AbstractFoo {
protected Long id;
protected String color;
//...
}
// Abstract Decorator(wraps foo)
public abstract class FooDecorator extends AbstractFoo {
// ...
}
That's just broken - why would you want each decorator to have it's own copy of the state?
Part of the problem, I think, is that you have confused the Decorator
pattern with the Adapter
pattern.
public interface Foo {
Long getId();
Color getColor();
LocalDateTime getLastModified();
}
public interface FooDTO {
Long getId();
String getColor();
LocalDateTime getLastMofified();
}
These are adapters:
public class FooDTOAdapter implements FooDTO {
private final Foo foo;
// ...
String getColor() {
return foo.getColor().toString();
}
}
public class FooAdapter implements Foo {
private final FooDTO dto;
// ...
Color getColor() {
return Color.parse(dto.getColor());
}
}
These are decorators, though they aren't very good ones -- see below
public class FooBarView implements FooDTO {
private final FooDTO dto;
//...
// Maps field to JSON as 'fooBarColor'
@JsonMap("fooBarColor")
public String getColor() {
return dto.getColor();
}
}
@Entity
@Table(name="TBL_FOO")
public class FooEntity implements FooDTO {
private final FooDTO dto;
// ...
@Column(name="DS_COLOR", length="255")
public String getColor(){
return super.getColor();
}
}
I think this approach is getting closer to what you want. For example, the signature of the Repository used by the model looks like:
interface FooRepository {
save(Foo foo);
}
And the implementation that connects the model to the entity store looks something like
class Connector implements FooRepository {
private final Store<FooEntity> entityStore;
//...
void save(Foo foo) {
FooDTO dto = new FooDTOAdapter(foo);
FooEntity entity = new FooEntity(dto);
entityStore.save(dto);
}
}
So the good news is that each of your components gets to look at the state through its preferred lens, without actually needing to copy any of the data.
You should be aware, though, that every time the data passes through a layer, the pearl gets bigger, as interfaces get swapped out to adapt to the new component. That's not good or bad by itself, it's just a thing to be aware of; because you are choosing not to copy the data at the interface, it is getting further away.
FooBarView was implemented as a decorator up above; it's not a good example because that's not the role that implementation plays (FooEntity has the same issue); you only wrap FooDTO in a FooBarView because you are about to hand it off to a serialization layer.
class FooBarViewWriter {
void writeTo(JsonWriter json, FooBarView view) {
// ...
}
}
That piece cares about the annotations, to do the magic, but it doesn't actually care about the FooDTO surface. This implementation would work just as well
public class FooBarView /* Not a FooDTO */ {
private final FooDTO dto;
//...
// Maps field to JSON as 'fooBarColor'
@JsonMap("fooBarColor")
public String getColor() {
return dto.getColor();
}
}
In other words, it is just an Adapter, that was accidentally written in the Decorator pattern. You get some compile time checks that you've implemented all of the signature, but not much else.
A more likely Decorator is one that adds an aspect to an implementation.
public class TimedFooRepository implements FooRepository {
private final FooRepository repo;
public void save(Foo foo) {
Timer timer = start();
try {
repo.save(foo);
} finally {
stop(timer);
}
}
// ...
}
Abstract Decorators usually come about when you have multiple implementations that are going to just dispatch the call to the inner layer. Rather than writing that code over and over, you write it once, and then let the concrete implementations pick and choose where they need to replace the default behavior
abstract class AbstractFooRepository implements FooRepository {
private final FooRepository repo;
protected AbstractFooRepository(FooRepository repo) {
this.repo = repo;
}
public void save(Foo foo) {
repo.save(foo);
}
// ...
}
public class TimedFooRepository extends AbstractFooRepository {
// No longer necessary to keep our own handle
/* private final FooRepository repo; */
public TimedFooRepository(FooRepository repo, ...) {
super(repo);
// ...
}
public void save(Foo foo) {
Timer timer = start();
try {
super.save(foo);
} finally {
stop(timer);
}
}
// ...
}
An abstract decorator with only a single method is pretty silly, since every implementation is going to rewrite that method. But it illustrates the idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With