开发者

Domain Driven Design Critique

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

  1. Create a new request with values
  2. is request approved?
    1. If yes, show the values
    2. 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(!appr开发者_JAVA百科ove) {
         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.


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 the License instead of the handler (possibly by giving it the ID). This is particularly true if there are properties of LicenseRequest 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 or LicenseRequest 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.


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

var license = licenseFactory.NewLicense();
response = license.Request(name, state)

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜