java—在ddd中创建价值对象的方法—哪种解决方案是最好的?

9avjhtql  于 2021-08-25  发布在  Java
关注(0)|答案(3)|浏览(243)

我试图了解一些不清楚的问题:
下面介绍的哪种组合对象/聚合的方法是最好的(下面的示例是简化的-通常我使用vavr和fp而不是抛出异常)? DescriptionValidator 只是一个港口。在他的实现中,我目前正在询问其他微服务 description 正确性。
(a)

public class Description {

    private final String text;

    public Description(String text) {
        this.text = text;
    }
}

public class Item {

    private final Description description;

    public Item(Description description) {
        this.description = description;
    }

}

public class ItemService {

    private final DescriptionValidator descriptionValidator;
    private final ItemRepo itemRepo;

    public void saveNewItem(String description) {
        if(descriptionValidator.isValid(description)) {
            itemRepo.save(new Item(new Description(description)));
        }
    }
}

(b)

public class Description {

    private final String text;

    public Description(String text) {
        this.text = text;
    }
}

public class Item {

    private final Description description;

    public Item(String description) {
        this.description = new Description(description);
    }

}

public class ItemService {

    private final DescriptionValidator descriptionValidator;
    private final ItemRepo itemRepo;

    public void saveNewItem(String description) {
        if(descriptionValidator.isValid(description)) {
            itemRepo.save(new Item(description));
        }
    }
}

(c)

public class Description {

    private final String text;

    private Description(String text) {
        this.text = text;
    }

    public static Description validated(String text, DescriptionValidator validator) {
        if(validator.isValid(text)) {
            return new Description(text);
        }

        throw new RuntimeException();
    }
}

public class Item {

    private final Description description;

    public Item(String description, DescriptionValidator validator) {
        this.description = Description.validated(description, validator);
    }

}

public class ItemService {

    private final DescriptionValidator descriptionValidator;
    private final ItemRepo itemRepo;

    public void saveNewItem(String description) {
        itemRepo.save(new Item(description, descriptionValidator));
    }
}

或(d)

public class Description {

    private final String text;

    private Description(String text) {
        this.text = text;
    }

    public static Description validated(String text, DescriptionValidator validator) {
        if(validator.isValid(text)) {
            return new Description(text);
        }

        throw new RuntimeException();
    }
}

public class Item {

    private final Description description;

    public Item(Description description) {
        this.description = description;
    }

}

public class ItemService {

    private final DescriptionValidator descriptionValidator;
    private final ItemRepo itemRepo;

    public void saveNewItem(String description) {
        itemRepo.save(new Item(Description.validated(description, descriptionValidator)));
    }
}
7tofc5zh

7tofc5zh1#

tl;dr:解析,不验证
我们通常更喜欢将非结构化/通用输入转换为边界附近的结构化输入。这有两个原因-第一,这是代码中最有可能正确报告问题的部分,第二,这是代码中实际知道预期消息格式的部分。
例如,如果我们正在处理http表单提交之类的事情,我们通常希望验证我们在http请求中找到的数据是否符合我们的消息传递模式(也就是说,此服务器与其客户端之间关于消息的稳定协议)。
从消息模式到域模型所期望的内存中表示的转换实际上是一个单独的问题。
第二个问题应该在应用程序层中解决,也就是说,这是应用程序代码的责任(了解存储库和事务语义的同一“层”),以此类推)将messageschema.description转换为domainmodel.description(并处理这两个概念实际上不一致的任何边缘情况)。
域值内的验证本身是一种防御性编程策略,用于确保domainmodel.description内的数据结构满足所有必要的前提条件,以确保描述方法返回一致的结果。
马匹在球场上奔跑。

gg0vcinb

gg0vcinb2#

我将使用第二个代码,其中包含一个更现实的示例。在整个描述中,我将普遍使用的语言称为ul。下面是我从这段代码中学到的一些对设计很重要的东西
描述是一个值对象
项目是拥有描述的实体(因此,用ul的说法: An Item has a description )
一个项目包含一个描述
现在让我们看看上面列出的上下文中的不变量。
描述不能为空:在ul中,此不变量将转换为 Items always have a description . 检查这种不变量的最佳方法是在项本身的构造函数中。

public class Item {
    ...
        public Item(string description) {
            if (description == null) throw NoDescriptionException();
        }
    }

不能太长-这是一个有趣的问题,因为你没有提供足够的信息,让我收集如何在你的ul中体现这一点。我将假设最基本的ul表示,并说您的意图是 Items have a max description of X length because <<Insert reason here>> . 在这种情况下,检查将再次进入项构造函数。

public class Item {
    ...
        private int MaxLength = 200;
        public Item(string description) {
            if (description == null) throw NoDescriptionException();
            if (description.length > MaxLength) throw DescriptionTooLongException();
        }
    }

不能包含亵渎:在这种情况下,可能涉及检查亵渎的逻辑,并包含最有可能超出项目实体边界的内容。除了服务,这种逻辑不太适合任何概念。所以

public class ProfanityService implements IProfanityService
    {
       ...
       public bool ContainsProfanity(string text){...}
    }
    public class Item {
    ...
        public Item(Description description, IProfanityService profanityService) {
            if (description == null) throw NoDescriptionException();
            if (description.length > MaxLength) throw DescriptionTooLongException();
            if (profanityService.containsProfanity(description)) throw TooProfaneException();
        }
    }

有一个反对在构造函数中加入亵渎检查器之类的东西的论点,如果有人因此对我推诿,我就不会为了保留这个设计而太努力了。还有其他方法可以处理它,只要您将此类检查的逻辑包含在比项的上下文更有意义的上下文中,它们就同样健壮。
关于上面的示例代码,请注意以下几点:
我甚至还没有创建描述类,因为不需要它。描述可以作为本机字符串保留,直到我的ul公开了另一个要求。
我没有创建服务来处理实体的持久性,因为实体不属于服务。在ddd中实现持久性的动态是相对复杂的,有许多不同的方法来剥猫皮。我通常有一个存储库,其唯一目的是持久化实体。因此,我构建新实体(如果足够简单,直接通过“新建”或使用工厂),并将该实体传递到存储库(通过应用程序服务中的调用)
只有当ul描述的概念不适用于其他任何地方时,我才会使用服务。您希望避免创建贫血模型。您希望创建表示ul的富域实体。我对您的代码所做的另一个更改是在id本身内部为项目生成id,以进一步说明这一点。我将在item类的构造函数中执行此操作。

tyg4sfes

tyg4sfes3#

@原因之声
谢谢你的回答。我已经读了好几遍了,但还是有问题。
我有标准的请求流:
rest控制器->项目服务->项目存储库->项目服务->rest控制器
itemservice是一个门面,这是我的域模块的入口。我知道我应该加上这样的东西 ItemApplicationService 在rest控制器和itemfacade之间,我添加了事务管理,但为了简化,我有一个 ItemService 其中我添加了@transactional注解。
我的http表单数据- ItemRestDto 首先通过框架进行验证。换句话说,框架检查 dto 具有有效的json格式。
如果可以的话,我的 ItemService 可能会被处决。
如果我理解正确,我就必须创造 Description 在开始 ItemService::createItem 对因为您写过“我们通常更喜欢将非结构化/通用输入转换为边界附近的结构化输入。”在我看来,这里是我系统的边界。
好的,我已经从我的工作中准备了更现实的例子。我的 Description 有一些不变量:
不能为空
不能太久
不能包含亵渎,但目前已由其他微服务进行检查。
我想准备 Description api的每一次创建都需要 DescriptionSafetyTester . 我有一些灵活性,因为将来我可以将检查亵渎的内容从其他微服务转移到当前的微服务,这对我的领域是透明的。
正如我之前承诺的,这是一个更现实的例子,你能评估一下吗?
java-rest http控制器

@RestController
public class ItemController {

    private final ItemService itemService;

    @PostMapping("/item")
    String createItem(@RequestBody ItemRestDto itemRestDto) {
        return itemService.createItem(itemRestDto);
    }
}

java-RESTAPI请求

public class ItemRestDto {

    private String description;

    public String getDescription() {
        return description;
    }
}

java-应用程序服务和我的模块条目

public class ItemService {

    private final ItemRepository itemRepository;
    private final TextSafetyTester textSafetyTester;

    @Transactional
    public String createItem(ItemRestDto itemRestDto) {
        final ItemId itemId = ItemId.generate();
        final Description description = Description.asSafe(itemRestDto.getDescription(), textSafetyTester);
        final Item saved = itemRepository.save(new Item(itemId, description));
        return saved.getId().getValue();
    }
}

textsafetytester.java-端口

public interface TextSafetyTester {

    boolean isSafe(String text);

}

somemicroserviceclient.java-适配器

public class SomeMicroserviceClient implements TextSafetyTester{

    private final HttpClient httpClient;

    @Override
    public boolean isSafe(String text) {
        // call HTTP
        return true;
    }
}

description.java

public class Description {

    private static final int MAX_LENGTH = 255;

    private final String value;

    private Description(String value) {
        this.value = value;
    }

    public static Description asSafe(String rawValue, TextSafetyTester tester) {
        if (Objects.isNull(rawValue)) {
            throw new RuntimeException("Description cannot be null");
        }

        if (rawValue.length() > MAX_LENGTH) {
            throw new RuntimeException("Description is too long");
        }

        if (!tester.isSafe(rawValue)) {
            throw new RuntimeException("Description is not safe");
        }

        return new Description(rawValue);
    }
}


item.java-实体/聚合

public class Item { // this is domain entity

    private final ItemId id;
    private final Description description;

    public Item(ItemId id, Description description) {
        this.id = id;
        this.description = description;
    }

    public ItemId getId() {
        return id;
    }
}

相关问题