【发布时间】:2020-11-23 19:18:30
【问题描述】:
我有以下代码规范示例,它在 Photoshop 中对图像进行建模。
图片以PhotoshopImage 给出。每个图像都有Layers,它是一个包含图像构成的所有层的对象,在我的例子中它只包含两层 - 第一个是实体层(DefaultLayer 的实例),第二个是透明层(实例NotifiableLayer)。每当DefaultLayer 更新时,我们还必须更新NotifiableLayer,它正在监听DefaultLayer(即下方)上的更改,以便它可以自我更新(例如当您更新下面图层上的一些黑色像素时,然后透明不透明度为 50% 且位于该较低层之上的层将显示该像素为灰色)。
实现如下:
public class ES2 {
public static void main(String[] args) {
PhotoshopImage image = new PhotoshopImage();
//draw ine black pixel at position 1,1 in layer 1 (top transparent layer)
DrawOneBlackPixelCommand command1 = new DrawOneBlackPixelCommand(1,1,new Coordinates(1,1));
image.drawOneBlackPixel(command1);
//draw one black pixel at position 0,0 in layer 0 (bottom solid layer)
//this command will also affect transparent layer 1 via callback
DrawOneBlackPixelCommand command2 = new DrawOneBlackPixelCommand(1,0,new Coordinates(0,0));
image.drawOneBlackPixel(command2);
int[][] imagePixels = image.getImagePixels();
//[2, 0]
//[0, 1]
System.out.println(Arrays.toString(imagePixels[0]));
System.out.println(Arrays.toString(imagePixels[1]));
}
}
record DrawOneBlackPixelCommand(
int imageId,
int layerType,
Coordinates pixelCoordinates
){}
record Coordinates(int x, int y){}
class PhotoshopImage{
Integer imageId = 1;
String imageName = "someName";
LocalDateTime dateTime = LocalDateTime.now();
Layers layers;
PhotoshopImage(){
layers = new Layers();
}
void drawOneBlackPixel(DrawOneBlackPixelCommand command){
if(LocalDateTime.now().isBefore(dateTime)){
throw new DrawingPixelTimeExpiredException();
}
layers.drawOneBlackPixel(command.layerType(), command.pixelCoordinates());
}
int[][] getImagePixels(){
return layers.getVisibleLayerPixels();
}
class DrawingPixelTimeExpiredException extends RuntimeException{}
}
class Layers{
Set<NotifiableLayer> notifiableLayerObservers = new HashSet<>();
NavigableMap<Integer, Layer> layers = new TreeMap<>();
Layers(){
DefaultLayer solid = new DefaultLayer();
NotifiableLayer transparent = new NotifiableLayer();
layers.put(0, solid);
layers.put(1, transparent);
notifiableLayerObservers.add(transparent);
}
void drawOneBlackPixel(int layerType, Coordinates pixelCoordinates){
if(!layers.containsKey(layerType)){
throw new LayerDoesNotExistException();
}
Layer change = layers.get(layerType);
change.drawOneBlackPixel(pixelCoordinates);
notifiableLayerObservers.forEach(l -> l.notifyLayer(change, pixelCoordinates));
}
public int[][] getVisibleLayerPixels() {
return layers.lastEntry().getValue().getLayerPixels();
}
class LayerDoesNotExistException extends RuntimeException{}
}
interface Layer{
void drawOneBlackPixel(Coordinates coordinates);
int[][] getLayerPixels();
}
class DefaultLayer implements Layer{
int[][] pixels = new int[][]{{0,0},{0,0}};
@Override
public void drawOneBlackPixel(Coordinates c) {
pixels[c.x()][c.y()] = 1;
}
@Override
public int[][] getLayerPixels() {
return pixels;
}
}
class NotifiableLayer implements Layer{
int[][] pixels = new int[][]{{0,0},{0,0}};
void notifyLayer(Layer changed, Coordinates c){
//if it is not this layer, then it is layer below (solid layer)
if(changed!=this){
int pixelInLayerBelow = changed.getLayerPixels()[c.x()][c.y()];
syncPixelWithLayerBelow(pixelInLayerBelow, c);
}
}
private void syncPixelWithLayerBelow(int pixelBelow, Coordinates c){
pixels[c.x()][c.y()] = pixelBelow + 1;
}
@Override
public void drawOneBlackPixel(Coordinates c) {
pixels[c.x()][c.y()] = 1;
}
@Override
public int[][] getLayerPixels() {
return pixels;
}
}
现在,这被实现为可变状态对象(即 - 它不使用事件源)。无论我阅读什么有关事件溯源的手册,它都仅基于一些超级简单的示例。
就我而言-我不知道如何创建事件OneBlackPixelDrawnEvent(一种方法是在下面的更新答案中,但对于 ES 带来的好处来说它看起来太复杂了)-这应该是代码中这两个操作的结果,以及如何应用这些事件 - 应该在PhotoshopImage 中应用,还是每个层都应该负责更新其部分状态?如何将这些事件从 PhotoshopImage 聚合转发到 Layers 并进一步向下?
更新 - 使用事件溯源的一种实施方式示例
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
public class ES2 {
public static void main(String[] args) {
PhotoshopImage image = new PhotoshopImage();
//draw ine black pixel at position 1,1 in layer 1 (top transparent layer)
DrawOneBlackPixelCommand command1 = new DrawOneBlackPixelCommand(1,1,new Coordinates(1,1));
List<Event> events1 = image.drawOneBlackPixel(command1);
//[OneBlackPixelDrawnEvent[layerType=1, pixelCoordinates=Coordinates[x=1, y=1], pixelValue=1]]
System.out.println(events1);
//draw one black pixel at position 0,0 in layer 0 (bottom solid layer)
//this command will also affect transparent layer 1 via callback
DrawOneBlackPixelCommand command2 = new DrawOneBlackPixelCommand(1,0,new Coordinates(0,0));
List<Event> events2 = image.drawOneBlackPixel(command2);
//[OneBlackPixelDrawnEvent[layerType=0, pixelCoordinates=Coordinates[x=0, y=0], pixelValue=1], LayerSyncedEvent[layerType=1, pixelCoordinates=Coordinates[x=0, y=0], pixelValue=2]]
System.out.println(events2);
int[][] imagePixels = image.getImagePixels();
//[2, 0]
//[0, 1]
System.out.println(Arrays.toString(imagePixels[0]));
System.out.println(Arrays.toString(imagePixels[1]));
}
}
interface Event{}
record DrawOneBlackPixelCommand(
int imageId,
int layerType,
Coordinates pixelCoordinates
){}
record Coordinates(int x, int y){}
record OneBlackPixelDrawnEvent(
Integer layerType,
Coordinates pixelCoordinates,
Integer pixelValue
) implements Event{}
class PhotoshopImage{
Integer imageId = 1;
String imageName = "someName";
LocalDateTime dateTime = LocalDateTime.now();
Layers layers;
PhotoshopImage(){
layers = new Layers();
}
List<Event> drawOneBlackPixel(DrawOneBlackPixelCommand command){
if(LocalDateTime.now().isBefore(dateTime)){
throw new DrawingPixelTimeExpiredException();
}
List<Event> events = layers.drawOneBlackPixel(command.layerType(), command.pixelCoordinates());
apply(events); //Only here we can update state of this aggregate, so it is not updated twice
return events;
}
void apply(List<Event> events){
layers.apply(events);
}
int[][] getImagePixels(){
return layers.getVisibleLayerPixels();
}
class DrawingPixelTimeExpiredException extends RuntimeException{}
}
class Layers{
Map<Integer, NotifiableLayer> notifiableLayerObservers = new HashMap<>();
NavigableMap<Integer, Layer> layers = new TreeMap<>();
Layers(){
DefaultLayer solid = new DefaultLayer();
NotifiableLayer transparent = new NotifiableLayer();
layers.put(0, solid);
layers.put(1, transparent);
notifiableLayerObservers.put(1, transparent);
}
List<Event> drawOneBlackPixel(int layerType, Coordinates pixelCoordinates){
if(!layers.containsKey(layerType)){
throw new LayerDoesNotExistException();
}
Layer change = layers.get(layerType);
OneBlackPixelDrawnEvent event = change.drawOneBlackPixel(pixelCoordinates);
//Here, I have to add layerType, since it is a missing info on event!
OneBlackPixelDrawnEvent updatedEvent = new OneBlackPixelDrawnEvent(layerType, event.pixelCoordinates(), event.pixelValue());
List<LayerSyncedEvent> syncedEvents = notifiableLayerObservers.entrySet().stream()
.map(en ->
en.getValue()
.notifyLayer(change, updatedEvent)
//Here we have to re-pack event, since it is missing some info that can be
//filled only on this level
.map(e -> new LayerSyncedEvent(en.getKey(), e.pixelCoordinates(), e.pixelValue()))
)
.flatMap(Optional::stream)
.collect(Collectors.toList());
List<Event> results = new ArrayList<>();
results.add(updatedEvent);
results.addAll(syncedEvents);
//apply(results); we still cannot apply here, since applying in aggregate root would apply twice!
return results;
}
public void apply(List<Event> events){
for(Event e : events){
if(e instanceof LayerSyncedEvent ev){
layers.get(ev.layerType()).apply(ev);
}
if(e instanceof OneBlackPixelDrawnEvent ev){
layers.get(ev.layerType()).apply(ev);
}
}
}
public int[][] getVisibleLayerPixels() {
return layers.lastEntry().getValue().getLayerPixels();
}
class LayerDoesNotExistException extends RuntimeException{}
}
interface Layer{
OneBlackPixelDrawnEvent drawOneBlackPixel(Coordinates coordinates);
int[][] getLayerPixels();
<T extends Event> void apply(T e);
}
class DefaultLayer implements Layer{
int[][] pixels = new int[][]{{0,0},{0,0}};
@Override
public OneBlackPixelDrawnEvent drawOneBlackPixel(Coordinates c) {
OneBlackPixelDrawnEvent event = new OneBlackPixelDrawnEvent(null, c, 1);
//apply(event); ! Since applying in aggregate root - cannot apply here!
return event;
}
@Override
public int[][] getLayerPixels() {
return pixels;
}
@Override
public <T extends Event> void apply(T e) {
if(e instanceof OneBlackPixelDrawnEvent ev){
Coordinates c = ev.pixelCoordinates();
pixels[c.x()][c.y()] = ev.pixelValue();
}
}
}
record LayerSyncedEvent(
Integer layerType,
Coordinates pixelCoordinates,
Integer pixelValue
) implements Event{}
class NotifiableLayer implements Layer{
int[][] pixels = new int[][]{{0,0},{0,0}};
Optional<LayerSyncedEvent> notifyLayer(Layer changed, OneBlackPixelDrawnEvent event){
//if it is not this layer, then it is layer below (solid layer)
if(changed!=this){
Coordinates c = event.pixelCoordinates();
//Since layer is not updated anymore in-place, we have to take changes from event!
//int pixelInLayerBelow = changed.getLayerPixels()[c.x()][c.y()];
int pixelInLayerBelow = event.pixelValue();
return Optional.of(syncPixelWithLayerBelow(pixelInLayerBelow, c));
}
return Optional.empty();
}
private LayerSyncedEvent syncPixelWithLayerBelow(int pixelBelow, Coordinates c){
LayerSyncedEvent event = new LayerSyncedEvent(null, c, pixelBelow + 1);
//apply(event); ! Since applying in aggregate root - cannot apply here!
return event;
}
@Override
public OneBlackPixelDrawnEvent drawOneBlackPixel(Coordinates c) {
OneBlackPixelDrawnEvent event = new OneBlackPixelDrawnEvent(null, c, 1);
//apply(event); ! Since applying in aggregate root - cannot apply here!
return event;
}
@Override
public int[][] getLayerPixels() {
return pixels;
}
@Override
public <T extends Event> void apply(T e) {
if(e instanceof LayerSyncedEvent ev){
Coordinates c = ev.pixelCoordinates();
pixels[c.x()][c.y()] = ev.pixelValue();
}
if(e instanceof OneBlackPixelDrawnEvent ev){
Coordinates c = ev.pixelCoordinates();
pixels[c.x()][c.y()] = ev.pixelValue();
}
}
}
我刚刚更新了这里的示例,使用一种实现聚合根的方法,以及返回事件的方法。我想这是一种可能的实现方式——但看看现在这有多复杂;即使是这个简单的例子——复杂性也增加了 2 倍。是我做错了什么,还是在事件源系统中这不是那么容易做到的?
【问题讨论】:
-
无法使用有效的赏金迁移。
-
你可能“做错了什么”,但事件溯源并不容易(simple 但不是easy),并且操作事件溯源模型 - - 特别是在规模上 - 也带来了许多其他挑战(例如,缓存失效)。在重视时间查询的企业信息系统中,收益可能超过成本。
-
@VoiceOfUnreason 你有什么建议可以让它变得更好吗?似乎我拥有的所有其他 ES 选项都带来了更多的复杂性。当你想使用 ES 并获得它带来的好处时,也许它必须如此复杂......
标签: java domain-driven-design cqrs event-sourcing axon