层间通信装饰器
Decorators For Inter-Layer Communication
我正在尝试通过使用抽象模型和装饰器来实现一种新的层间通信架构方法。
通常,当我们设计单体应用程序的层时,我们会有一个应用程序(控制器)层、一个域(业务)层和一个基础设施(持久性)层。这些层通过具体的模型定义相互通信,当从数据库中读取时,通过存储库导航,然后导航到服务,最后导航到将其分发给客户端的控制器。
话虽如此,让我们进入正题...
通常情况下,基础架构模型不得与业务模型相同,也不得与 UI 模型相同。
基础架构模型可能只关心它的目标存储库结构,域层可能只关心业务操作,UI 层必须只关心它的客户端和数据 read/input.
所有这些层必须 "understand" 相互抽象,甚至实现双向值映射(或 DTO),以便它们之间进行通信。 Mapping/DTO 是一种糟糕的方法,因为我们会对数据映射进行不必要的处理。
所以,这就是我正在尝试做的事情:通过使用装饰器进行通信来分离层。
在那种方法中,我们将在共享模块上拥有抽象组件及其装饰器,并且每一层都有自己的装饰器。
例如:
- AbstractFoo 是一个抽象;
- FooDecorator 实现了 AbstractFoo 并包装了一个 AbstractFoo;
- FooEntity 是一个实现 FooDecorator 并处理持久性内容的基础结构模型
- FooBusiness 是一种实现 FooEntity 装饰器并处理业务内容的业务模型
- FooView 是一个控制器模型,它实现了 FooBusiness 装饰器并处理 UI 字段、编码等...
通过这种方法我们可以:
- 内存中只有1个对象持有Foo信息,但是每一层
"minds it's own business".
- 不同模块层分离,业务只引入infra,app层只引入业务。
- 我们可以在不放弃我们的情况下为愚蠢的逻辑分发存根
域,就像只能知道摘要的购物车服务
产品,但不是域对象本身。
- 我们可以让不同的团队在应用程序的不同层工作而不会发生冲突。
这是纸上的例子:
那么...我所要求的是建议、提示和一些意见,如果这是一个好的方法。
[更新 #1]:
为了简化我的问题,我将 post 下面的用例:
共享组件:
// 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);}
}
基础设施层:
// 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();
}
}
域(业务)层
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();
}
}
应用程序(视图)层
/**
* 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.
我持怀疑态度。叫它两个 9。
在架构上,您的草图表明这些孤立的组件是对等的,我认为这根本不是对关注点建模的实用方法。域对业务非常重要。持久性,不是那么多(如果我们有足够的内存,并且可以保留我们的服务器 运行,我们就不会打扰)。
此外,还有一些相互渗透的设计选择;如果你选择的持久化是存储事件历史,那么你的领域模型和你的存储库必须就此达成一致,并且这两个组件之间的契约应该明确这一点 - 然而 none 你的其他组件关心如何事物的状态已经实现,他们只需要一些查询表面。
甚至可能不是 - 应用程序使用表示而不是对象来响应来自客户端的查询;如果这些表示提前缓存 (CQRS) 那么它根本不会关心域模型中的对象。
最重要的是,我认为所绘制的体系结构使依赖项的真正复杂性变得微不足道。认识到这些是具有不同关注点的不同组件的部分意义在于,您可以将它们相互交换。每个 api 更改都不应该是重建世界的事件(想想语义版本控制)。
添加示例代码后添加的说明
// Abstract Foo
public abstract class AbstractFoo {
protected Long id;
protected String color;
//...
}
// Abstract Decorator(wraps foo)
public abstract class FooDecorator extends AbstractFoo {
// ...
}
这太糟糕了 - 为什么您希望每个装饰器都有自己的状态副本?
我认为部分问题在于您将 Decorator
模式与 Adapter
模式混淆了。
public interface Foo {
Long getId();
Color getColor();
LocalDateTime getLastModified();
}
public interface FooDTO {
Long getId();
String getColor();
LocalDateTime getLastMofified();
}
这些是适配器:
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());
}
}
这些是 装饰器,尽管它们不是很好 -- 见下文
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();
}
}
我认为这种方法越来越接近您想要的。例如,模型使用的 Repository 的签名如下所示:
interface FooRepository {
save(Foo foo);
}
将模型连接到实体商店的实现类似于
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);
}
}
所以好消息是您的每个组件都可以通过其首选镜头查看状态,而无需实际复制任何数据。
不过,您应该知道,每次数据通过一层时,珍珠都会变大,因为界面会被换出以适应新组件。这本身并无好坏之分,只是需要注意的事情;因为你在界面选择不复制数据,所以越来越远了。
FooBarView 是作为上面的装饰器实现的;这不是一个很好的例子,因为这不是实现所扮演的角色(FooEntity 有同样的问题);您只将 FooDTO 包装在 FooBarView 中,因为您将把它交给序列化层。
class FooBarViewWriter {
void writeTo(JsonWriter json, FooBarView view) {
// ...
}
}
那篇文章关心注释,做魔术,但实际上并不关心 FooDTO 表面。此实现同样有效
public class FooBarView /* Not a FooDTO */ {
private final FooDTO dto;
//...
// Maps field to JSON as 'fooBarColor'
@JsonMap("fooBarColor")
public String getColor() {
return dto.getColor();
}
}
换句话说,它只是一个Adapter,是在Decorator模式中不小心写的。您会得到一些编译时检查,以确保您已实现所有签名,但除此之外就不多了。
更有可能的装饰器是为实现添加 方面。
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 通常在您有多个实现将调用分派到内层时出现。与其一遍又一遍地编写代码,不如编写一次,然后让具体的实现选择需要替换默认行为的地方
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);
}
}
// ...
}
只有一个方法的抽象装饰器非常愚蠢,因为每个实现都将重写该方法。但它说明了这个想法。
我正在尝试通过使用抽象模型和装饰器来实现一种新的层间通信架构方法。
通常,当我们设计单体应用程序的层时,我们会有一个应用程序(控制器)层、一个域(业务)层和一个基础设施(持久性)层。这些层通过具体的模型定义相互通信,当从数据库中读取时,通过存储库导航,然后导航到服务,最后导航到将其分发给客户端的控制器。
话虽如此,让我们进入正题...
通常情况下,基础架构模型不得与业务模型相同,也不得与 UI 模型相同。 基础架构模型可能只关心它的目标存储库结构,域层可能只关心业务操作,UI 层必须只关心它的客户端和数据 read/input.
所有这些层必须 "understand" 相互抽象,甚至实现双向值映射(或 DTO),以便它们之间进行通信。 Mapping/DTO 是一种糟糕的方法,因为我们会对数据映射进行不必要的处理。
所以,这就是我正在尝试做的事情:通过使用装饰器进行通信来分离层。
在那种方法中,我们将在共享模块上拥有抽象组件及其装饰器,并且每一层都有自己的装饰器。
例如:
- AbstractFoo 是一个抽象;
- FooDecorator 实现了 AbstractFoo 并包装了一个 AbstractFoo;
- FooEntity 是一个实现 FooDecorator 并处理持久性内容的基础结构模型
- FooBusiness 是一种实现 FooEntity 装饰器并处理业务内容的业务模型
- FooView 是一个控制器模型,它实现了 FooBusiness 装饰器并处理 UI 字段、编码等...
通过这种方法我们可以:
- 内存中只有1个对象持有Foo信息,但是每一层 "minds it's own business".
- 不同模块层分离,业务只引入infra,app层只引入业务。
- 我们可以在不放弃我们的情况下为愚蠢的逻辑分发存根 域,就像只能知道摘要的购物车服务 产品,但不是域对象本身。
- 我们可以让不同的团队在应用程序的不同层工作而不会发生冲突。
这是纸上的例子:
那么...我所要求的是建议、提示和一些意见,如果这是一个好的方法。
[更新 #1]:
为了简化我的问题,我将 post 下面的用例:
共享组件:
// 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);}
}
基础设施层:
// 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();
}
}
域(业务)层
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();
}
}
应用程序(视图)层
/**
* 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.
我持怀疑态度。叫它两个 9。
在架构上,您的草图表明这些孤立的组件是对等的,我认为这根本不是对关注点建模的实用方法。域对业务非常重要。持久性,不是那么多(如果我们有足够的内存,并且可以保留我们的服务器 运行,我们就不会打扰)。
此外,还有一些相互渗透的设计选择;如果你选择的持久化是存储事件历史,那么你的领域模型和你的存储库必须就此达成一致,并且这两个组件之间的契约应该明确这一点 - 然而 none 你的其他组件关心如何事物的状态已经实现,他们只需要一些查询表面。
甚至可能不是 - 应用程序使用表示而不是对象来响应来自客户端的查询;如果这些表示提前缓存 (CQRS) 那么它根本不会关心域模型中的对象。
最重要的是,我认为所绘制的体系结构使依赖项的真正复杂性变得微不足道。认识到这些是具有不同关注点的不同组件的部分意义在于,您可以将它们相互交换。每个 api 更改都不应该是重建世界的事件(想想语义版本控制)。
添加示例代码后添加的说明
// Abstract Foo
public abstract class AbstractFoo {
protected Long id;
protected String color;
//...
}
// Abstract Decorator(wraps foo)
public abstract class FooDecorator extends AbstractFoo {
// ...
}
这太糟糕了 - 为什么您希望每个装饰器都有自己的状态副本?
我认为部分问题在于您将 Decorator
模式与 Adapter
模式混淆了。
public interface Foo {
Long getId();
Color getColor();
LocalDateTime getLastModified();
}
public interface FooDTO {
Long getId();
String getColor();
LocalDateTime getLastMofified();
}
这些是适配器:
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());
}
}
这些是 装饰器,尽管它们不是很好 -- 见下文
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();
}
}
我认为这种方法越来越接近您想要的。例如,模型使用的 Repository 的签名如下所示:
interface FooRepository {
save(Foo foo);
}
将模型连接到实体商店的实现类似于
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);
}
}
所以好消息是您的每个组件都可以通过其首选镜头查看状态,而无需实际复制任何数据。
不过,您应该知道,每次数据通过一层时,珍珠都会变大,因为界面会被换出以适应新组件。这本身并无好坏之分,只是需要注意的事情;因为你在界面选择不复制数据,所以越来越远了。
FooBarView 是作为上面的装饰器实现的;这不是一个很好的例子,因为这不是实现所扮演的角色(FooEntity 有同样的问题);您只将 FooDTO 包装在 FooBarView 中,因为您将把它交给序列化层。
class FooBarViewWriter {
void writeTo(JsonWriter json, FooBarView view) {
// ...
}
}
那篇文章关心注释,做魔术,但实际上并不关心 FooDTO 表面。此实现同样有效
public class FooBarView /* Not a FooDTO */ {
private final FooDTO dto;
//...
// Maps field to JSON as 'fooBarColor'
@JsonMap("fooBarColor")
public String getColor() {
return dto.getColor();
}
}
换句话说,它只是一个Adapter,是在Decorator模式中不小心写的。您会得到一些编译时检查,以确保您已实现所有签名,但除此之外就不多了。
更有可能的装饰器是为实现添加 方面。
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 通常在您有多个实现将调用分派到内层时出现。与其一遍又一遍地编写代码,不如编写一次,然后让具体的实现选择需要替换默认行为的地方
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);
}
}
// ...
}
只有一个方法的抽象装饰器非常愚蠢,因为每个实现都将重写该方法。但它说明了这个想法。