领域驱动设计批评
我想对下面的领域驱动设计提出一些建议和批评。我在下面包含了伪代码。真正的代码将具有封装的属性。
担忧
我唯一担心的是它似乎贫血。
步骤
- 创建一个带有值的新请求
- 请求是否已获得批准?
- 如果是,请显示值
- 如果否,请说明未获批准的原因
类
enum UnitedStatesState {
ALABAMA,
//...
CALIFORNIA,
//...
MAINE,
//...
WASHINGTON
}
class License {
int id;
String name;
//enum of state that the license is applicable in
UnitedStatesState state;
}
class LicenseRequest {
//the name of the person making the request
String name;
//enum of state to which the user is requesting a license in
UnitedStatesState state;
LicenseResponse submit()
{
//TODO: move creation of the rules out of this class
RuleGroup<LicenseRequest> ruleGroup = new RuleGroup<>();
ruleGroup.add(new StateExclusionLicenseRequestRule(UnitedStatesState.MAINE));
boolean approved = ruleGroup.execute(this);
if(approved) {
License license = createLiscense(request);
return new ApprovedLicenseResponse(license);
} else {
DeniedLicenseResponse response = new DeniedLicenseResponse();
response.rules = newArrayList(ruleGroup);
return response;
}
}
//TODO: move create license out of Request. maybe a factory class?
private License createLicense()
{
License license = LicenseIdGenerator.generate(this.state);
license.name = this.name;
license.state = this.state;
save(license);
return license;
}
}
//visitor for the rule
interface Rule<T> {
public boolean execute(T o);
public List<String> getMessages();
}
//rule that auto denies when the request is made in an excluded state
class StateExclusionLicenseRequestRule : Rule<LicenseRequest> {
public List<String> getMessages();
UnitedStatesState excludedState;
public boolean execute(LicenseRequest request) {
if(request.state == excludedState)
{
messages.add("No license for " + request.state + " is available at this time.");
return false;
}
return true;
}
}
//rule that groups all other rules
class RuleGroup<T> : Rule<T> {
public void addRule(Rule<T> rule);
public List<Rule<T>> getFailedRules();
public List<String> getMessages() {
List<String> messages = new ArrayList<>();
for(Rule<T> rule : rules) {
messages.addAll(rule.getMessages());
}
return messages;
}
public boolean execute(T o) {
List<Rule<T>> failedRules = new ArrayList<>(rules.size());
for(Rule<T> rule : rules) {
boolean approve = rule.execute(o);
if(!approve) {
failedRules.add(rule);
}
}
return !failedRules.isEmpty();
}
}
interface LicenseResponse {
boolean approved;
}
class ApprovedLicenseResponse : LicenseResponse {
License license;
}
class DeniedLicenseResponse : LicenseResponse {
private List<Rule<LicenseRequest>> rules;
public List<String> getMessages()
{
List<String> messages = new ArrayList<>();
for(Rule<LicenseRequest> rule : rules) {
messages.addAll(rule.getMessages());
}
return messages;
}
}
示例代码
request = new Request(name: 'Test', state: UnitedStatesState.CALIFORNIA)
response = request.submit()
if(response.approved)
{
out('Your request is approved');
out('license id = ' + reponse.id);
}
else
{
out('Your request was denied');
for(String message : response.messages)
{
out(message);
}
}
更新 1:背景
这只是我想要实现的模拟。这是一个简单的系统,用户在表格中输入有关自己的信息,然后他们会被批准或拒绝许可。批准后,可打印证书。
举例来说,唯一的规则是拒绝缅因州的许可证请求。
更新 2:重构规则并删除处理程序
我对上面的示例进行了一些修改。删除了处理程序并将所有代码移至 LicenseRequest。我还将批准/拒绝规则移至实现访问者模式的类。
I would like some advice and critique with the domain driven design below. I've included pseudocode below. The real code would have encapsulated properties.
Concerns
My only concern is that it appears to be anemic.
Steps
- Create a new request with values
- is request approved?
- If yes, show the values
- If no, show the reasons not approved
Classes
enum UnitedStatesState {
ALABAMA,
//...
CALIFORNIA,
//...
MAINE,
//...
WASHINGTON
}
class License {
int id;
String name;
//enum of state that the license is applicable in
UnitedStatesState state;
}
class LicenseRequest {
//the name of the person making the request
String name;
//enum of state to which the user is requesting a license in
UnitedStatesState state;
LicenseResponse submit()
{
//TODO: move creation of the rules out of this class
RuleGroup<LicenseRequest> ruleGroup = new RuleGroup<>();
ruleGroup.add(new StateExclusionLicenseRequestRule(UnitedStatesState.MAINE));
boolean approved = ruleGroup.execute(this);
if(approved) {
License license = createLiscense(request);
return new ApprovedLicenseResponse(license);
} else {
DeniedLicenseResponse response = new DeniedLicenseResponse();
response.rules = newArrayList(ruleGroup);
return response;
}
}
//TODO: move create license out of Request. maybe a factory class?
private License createLicense()
{
License license = LicenseIdGenerator.generate(this.state);
license.name = this.name;
license.state = this.state;
save(license);
return license;
}
}
//visitor for the rule
interface Rule<T> {
public boolean execute(T o);
public List<String> getMessages();
}
//rule that auto denies when the request is made in an excluded state
class StateExclusionLicenseRequestRule : Rule<LicenseRequest> {
public List<String> getMessages();
UnitedStatesState excludedState;
public boolean execute(LicenseRequest request) {
if(request.state == excludedState)
{
messages.add("No license for " + request.state + " is available at this time.");
return false;
}
return true;
}
}
//rule that groups all other rules
class RuleGroup<T> : Rule<T> {
public void addRule(Rule<T> rule);
public List<Rule<T>> getFailedRules();
public List<String> getMessages() {
List<String> messages = new ArrayList<>();
for(Rule<T> rule : rules) {
messages.addAll(rule.getMessages());
}
return messages;
}
public boolean execute(T o) {
List<Rule<T>> failedRules = new ArrayList<>(rules.size());
for(Rule<T> rule : rules) {
boolean approve = rule.execute(o);
if(!approve) {
failedRules.add(rule);
}
}
return !failedRules.isEmpty();
}
}
interface LicenseResponse {
boolean approved;
}
class ApprovedLicenseResponse : LicenseResponse {
License license;
}
class DeniedLicenseResponse : LicenseResponse {
private List<Rule<LicenseRequest>> rules;
public List<String> getMessages()
{
List<String> messages = new ArrayList<>();
for(Rule<LicenseRequest> rule : rules) {
messages.addAll(rule.getMessages());
}
return messages;
}
}
Sample Code
request = new Request(name: 'Test', state: UnitedStatesState.CALIFORNIA)
response = request.submit()
if(response.approved)
{
out('Your request is approved');
out('license id = ' + reponse.id);
}
else
{
out('Your request was denied');
for(String message : response.messages)
{
out(message);
}
}
Update 1 : Background
This is just a mock of what I would like to implement. This is a simple system where a user enters information into a form about themselves and they are approved or denied a license. After approval, a certificate is available for print.
For the sake of example, the only rule is that a request for a license in Maine is denied.
Update 2 : Refactor Rules and Remove Handler
I've made some modifications to the example above. Removed the Handler and moved all code to the LicenseRequest. I've also moved the Rules for approving/denying to classes implementing the vistor pattern.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不幸的是,一些更相关的代码没有显示,但我会研究哪些代码可以被推送到
LicenseRequest
中。特别是,LicenseRequest
可能会创建License
而不是处理程序(可能通过为其提供 ID)。如果LicenseRequest
的属性仅用于创建批准的许可证,则尤其如此。那么这些就不必暴露在吸气剂中。我还会让确定批准(可能使用其他名称)直接创建响应,而不是传递可写消息列表(仅在失败时使用)。
您应该寻找的气味是Feature Envy。特别是,应检查使用
License
或LicenseRequest
中的数据进行的任何计算,以确定该计算是否应在这些类中完成。数据对象(特别是不可变数据对象)有其用途,但您的担心是正确的。
Unfortunately some of the more relevant code is not shown, but I would look into see what code could be pushed into
LicenseRequest
. In particular,LicenseRequest
could possibly create theLicense
instead of the handler (possibly by giving it the ID). This is particularly true if there are properties ofLicenseRequest
that are only used in creating an approved license. These then do not have to be exposed with getters.I would also have
determineApproval
(possibly with another name) create the response directly instead of passing a writable message list (which is only used on failures).The smell you should be looking for is Feature Envy. In particular any calculation using data from
License
orLicenseRequest
should be checked to see if that calculation should instead be done in those classes.There is a purpose for data objects (particularly immutable data objects), but you are correct to be concerned.
为了可扩展性,状态应该是 ValueObjects。例如,当您需要将州的缩写与其名称等同时会发生什么?
看起来你的设计也没有真正的聚合根源。如果没有许可证可言,许可证请求就没有意义吗?在这种情况下,它应该通过许可证(或许可证服务)处理,而不是直接由调用代码(可能是 Web 服务器、控制台应用程序等)处理。
那么对于伪代码来说,类似的东西会
更有意义吗?当您拥有多种类型的驾照(例如商业司机驾照)时会发生什么?
此外,对于规则,而不是命令模式(执行),您可以通过将规则作为规范来获得更清晰的解决方案 - https://en.wikipedia.org/wiki/Specification_pattern
States should be ValueObjects for extensibility. For example, what happens when you need to equate a state's abbreviation with its name?
It also looks like your design doesn't really have aggregate roots. Is a LicenseRequest meaningless without a License to talk about? In that case it should be handled through the license (or a license service), and not directly by the calling code (which could be a web server, console app, etc).
So for pseudo code would something like
make more sense? What happens when you have multiple types of licenses, like Commercial Driver's?
Also, for rules, rather than a command pattern (execute) you could get a cleaner solution by having the Rules as Specifications - https://en.wikipedia.org/wiki/Specification_pattern