用于层间通信的装饰器

ggd*_*dio 4 java architecture design-patterns domain-driven-design

我试图通过使用抽象模型和装饰器来实现用于层间通信的新架构方法.

传统上,当我们设计单片应用程序的层时,我们将拥有应用程序(控制器)层,域(业务)层和基础架构(持久性)层.这些层通过具体的模型定义相互通信,当从数据库中读取时,导航通过存储库,然后导航到服务,最后导航到将其分派给客户端的控制器.

那就是说,让我们谈谈......

通常,基础结构模型不能与业务模型相同,业务模型也不能与UI模型相同.基础架构模型可能仅涉及其目标存储库结构,域层可能仅涉及业务操作,并且UI层必须仅关注其客户端和数据读取/输入.

所有这些层必须"理解"彼此的抽象,或甚至实现双向值映射(或DTO的),以便在它们之间进行通信.映射/ DTO是一种糟糕的方法,因为我们会对数据映射进行不必要的处理.

所以,这就是我要做的事情:通过使用装饰器进行通信来分离图层.

在那种方法中,我们将在共享模块上具有抽象组件和它的装饰器,并且每个层都具有它自己的装饰器.

例如.:

  • AbstractFoo是一个抽象;
  • FooDecorator实现了AbstractFoo并且还包装了一个AbstractFoo;
  • FooEntity是一个实现FooDecorator并处理持久性内容的基础架构模型
  • FooBusiness是一种实现FooEntity装饰器和处理业务的商业模型
  • FooView是一个控制器模型,它实现了FooBusiness装饰器并处理UI字段,编码等...

通过这种方法,我们可以:

  • 内存中只有一个对象持有Foo信息,但每一层都"关注它自己的业务".
  • 将图层分隔在不同的模块中,因此业务仅导入infra,而app层仅导入业务.
  • 我们可以在不放弃我们的域的情况下分发用于哑逻辑的存根,例如必须只知道产品摘要的购物车服务,而不是域对象本身.
  • 我们可以让不同的团队在应用程序的不同层中工作而不会发生冲突.

这是一个纸上的例子:

草图

所以......我问的是建议,提示和一些意见,如果这是一个好方法.

[更新#1]:

为了简化我的问题,我将在下面发布一个用例:

共享组件:

// 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);}
}
Run Code Online (Sandbox Code Playgroud)

基础设施层:

// 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();
   }

}
Run Code Online (Sandbox Code Playgroud)

域(业务)层

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();
   }

}
Run Code Online (Sandbox Code Playgroud)

应用(视图)图层

/**
 * 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();
   }

}
Run Code Online (Sandbox Code Playgroud)

-

// Pseudo Code
public class FooBarREST {
   // '/api/v1/foobar/{id}'
   public getFooBar(Long id) {
      return new FooBarView(find(id));
   }
}
Run Code Online (Sandbox Code Playgroud)

Voi*_*son 5

所以......我问的是建议,提示和一些意见,如果这是一个好方法.

我持怀疑态度.称它为两个9s.

在架构上,你的草图表明这些孤立的组件是同行的,我认为这根本不是建立关注点的实用方法.域对业务非常重要.持久性,而不是那么多(如果我们有足够的内存,并且可以保持我们的服务器运行,我们就不会打扰).

此外,有些设计选择相互渗透; 如果您选择持久性存储事件历史记录,那么您的域模型和您的存储库必须就此达成一致,并且这两个组件之间的契约应该是明确的 - 但是您的其他任何组件都不关心事物的状态实现后,他们只需要一些查询表面.

甚至可能不是 - 应用程序使用表示而不是对象来响应来自客户端的查询; 如果这些表示被提前缓存(CQRS),那么它根本不关心域模型中的对象.

最重要的是,我认为所绘制的架构使得依赖关系的真正复杂性变得微不足道.认识到这些是具有不同关注点的不同组件的部分原因在于您可以将它们彼此交换.每次api更改都不应该是重建世界事件(想想语义版本控制).

添加示例代码后添加了说明

// Abstract Foo
public abstract class AbstractFoo {
    protected Long id;
    protected String color;
    //...
}

// Abstract Decorator(wraps foo)
public abstract class FooDecorator extends AbstractFoo {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

那只是打破了 - 你为什么要让每个装饰者拥有它自己的状态副本?

我认为,部分问题在于您将Decorator模式与Adapter模式混淆.

public interface Foo {
    Long getId();
    Color getColor();
    LocalDateTime getLastModified();
}

public interface FooDTO {
    Long getId();
    String getColor();
    LocalDateTime getLastMofified();
}
Run Code Online (Sandbox Code Playgroud)

这些是适配器:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

这些是装饰者,虽然它们不是很好 - 见下文

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为这种方法越来越接近你想要的了.例如,模型使用的存储库签名如下所示:

interface FooRepository {
    save(Foo foo);
}            
Run Code Online (Sandbox Code Playgroud)

将模型连接到实体存储的实现看起来像

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以好消息是,每个组件都可以通过其首选镜头查看状态,而无需实际复制任何数据.

但是,您应该知道,每次数据通过图层时,珍珠都会变大,因为接口会被换出以适应新组件.这本身并不好或坏,这只是一个需要注意的事情; 因为您选择不在界面上复制数据,所以它会越来越远.

FooBarView在上面被实现为装饰器; 这不是一个很好的例子,因为这不是实现所扮演的角色(FooEntity有同样的问题); 你只在FooBarView中包装FooDTO,因为你要将它交给序列化层.

class FooBarViewWriter {
    void writeTo(JsonWriter json, FooBarView view) {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

那篇文章关注注释,做魔术,但实际上并不关心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();
    }
}
Run Code Online (Sandbox Code Playgroud)

换句话说,它只是一个适配器,它是在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);
        }
    }

    // ...
}   
Run Code Online (Sandbox Code Playgroud)

抽象装饰器通常是在你有多个实现只是将调用分派给内层时出现的.不是一遍又一遍地编写代码,而是编写一次,然后让具体的实现选择他们需要的地方来替换默认行为

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);
        }
    }

    // ...
}   
Run Code Online (Sandbox Code Playgroud)

只有一个方法的抽象装饰器非常愚蠢,因为每个实现都会重写该方法.但它说明了这个想法.