用于层间通信的装饰器

时间:2016-06-09 19:21:17

标签: java design-patterns architecture domain-driven-design

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

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

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

通常,基础架构模型不能与业务模型相同,业务模型也不能与UI模型相同。 基础架构模型可能只涉及它的目标存储库结构,域层可能只涉及业务操作,而UI层只能关注它的客户端和数据读取/输入。

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

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

在这种方法中,我们会在共享模块上使用抽象组件和装饰器,每个层都有自己的装饰器。

例如:

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

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

  • 内存中只有一个对象包含Foo信息,但是每一层都有 “介意它自己的事业。”
  • 将不同模块中的图层分开,因此业务仅导入infra,而应用层仅导入业务。
  • 我们可以分发哑巴逻辑的存根,而不会泄露我们的 域,就像购物车服务一样,只能知道摘要 一个产品,但不是域对象本身。
  • 我们可以让不同的团队在应用程序的不同层中工作而不会发生冲突。

以下是纸上的示例:

Sketch

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

[更新#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);}
}

基础设施层:

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

1 个答案:

答案 0 :(得分:5)

  

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

我持怀疑态度。称之为两个9。

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

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

甚至可能不是 - 应用程序使用表示而不是对象来响应来自客户端的查询;如果这些表示是提前缓存的(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();
    }
}

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

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有同样的问题);你只在FooBarView中包装FooDTO,因为你要把它交给一个序列化层。

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

换句话说,它只是一个适配器,它被意外地写在Decorator模式中。你得到了一些编译时间检查,你已经实现了所有的签名,但没有多少。

更有可能的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 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);
        }
    }

    // ...
}   

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