ã¡ã¨ãå°é£ãã話ã«ãªãã¾ããDDDã®è©±ãªãã§ã
コードで学ぶドメイン駆動設計入門 〜アグリゲート編〜 - じゅんいち☆かとうの技術日誌
ã§ãåãä¸ããã¢ã°ãªã²ã¼ãã«é¢ããèå¯ã§ããã©ãããã¹ããã¯ã¡ãã£ã¨åãã£ã¦ãã¾ããããããèªãã§ãããããããã®ã§ã¯ï¼ã¨æãæ¹ãæè¦æè¿ã§ããã¤ã³ã¿ã¼ãããã£ã¦ä¸æ¹ã ã¨é¢ç½ããªãã®ã§è°è«ãããã§ããã
ã¢ã°ãªã²ã¼ãã£ã¦ã©ã¤ããµã¤ã¯ã«ã®å¢çãæ±ãããã®è¨è¨ãã¿ã¼ã³ã§ããã©ã¤ããµã¤ã¯ã«ã§æãåºãã®ã¯ãªãã¸ããªã¨ãã¡ã¯ããªããã®äºã¤ã®ãªãã¸ã§ã¯ããæ±ãã®ãã¢ã°ãªã²ã¼ãã§ããã©ã¤ããµã¤ã¯ã«ã¯è³ãã¨ããã«åºã¦ããã®ã§ãçµæ§å¤§äºã§ãè¨è¨ã®æ ¹å¹¹ã«å½±é¿ãããªãã¸ã§ã¯ãã®ã²ã¨ã¤ã§ã¯ãªããã¨æãã¾ãã
ã¢ã°ãªã²ã¼ãã¯ãå é¨ã®ã¨ã³ãã£ãã£ãããªã¥ã¼ãªãã¸ã§ã¯ããéç´ãã¦ããå¢çã§ããã®å¢çã¯ã¨ã³ãã£ãã£ã§ãããã«ã¼ãã¨ã³ãã£ãã£ã¨å¼ã°ããã
ã°ãã¼ãã«ãªåä¸æ§ãæã¤ã¨ã³ãã£ãã£(éç´ã«ã¼ã)
ä¾ãã°ãä½åº¦ãç»å ´ãã¦ãã以ä¸ã®ãããªå¾æ¥å¡ã¨ã³ãã£ãã£ã¯ãã°ãã¼ãã«ãªåä¸æ§ãä¿è¨¼ããèå¥åã¨ããã以å¤ååãå±æ§ãéç´ãã¦ãã¾ããã¤ã¾ããã¢ã°ãªã²ã¼ãã§ããEmployeeã®ããã«ãéç´ãã¦ããå¤å´ã®ã°ãã¼ãã«ãªã¨ã³ãã£ãã£ããéç´ã«ã¼ãããã«ã¼ãã¨ã³ãã£ãã£ã¨å¼ã¶ããããããã§ã¯éç´ã«ã¼ãã«çµ±ä¸ãã¾ãã
// Employeeã¯ã¢ã°ãªã²ã¼ããã¿ã¼ã³ã public class Employee { private final UUID id; // ã°ãã¼ãã«ãªåä¸æ§ãä¿è¨¼ããèå¥å private String name; // VOãéç´ãã¦ãã private Department department; // VOãéç´ãã¦ãã public Employee(UUID id){ this.id = id; } public UUID getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Department getDepartment() { return department; } public void setDepartment(Department department) { this.department = department; } // hashCode, equalsã¯çç¥ }
ãã¼ã«ã«ãªåä¸æ§ãæã¤ãã¼ã«ã«ã¨ã³ãã£ãã£
éç´ã«ã¼ãå
é¨ã ãã§ãåä¸æ§ãä¿è¨¼ããèå¥ãæã¤ãã¼ã«ã«ãªã¨ã³ãã£ãã£ãããã¾ãããªã®ã§ãã¨ã³ãã£ãã£ï¼ã¢ã°ãªã²ã¼ãã¨ãã訳ã§ã¯ãªãã§ããã
ãã¨ãã°ã以ä¸ã®ãããªã¢ãã«ã
// Carã¨ã³ãã£ãã£(éç´ã«ã¼ã) public class Car implements Cloneable { private final String id; // ã°ãã¼ãã«ãªåä¸æ§ãä¿è¨¼ããèå¥å private Map<Position, Tire> tires = new HashMap<Position, Tire>(); public Car(String id) { Validate.notNull(id); this.id = id; } public String getId() { return id; } public void addTire(Tire tire) { tires.put(tire.getLocalId(), tire); } public Set<Tire> getTires() { Set<Tire> result = new HashSet<Tire>(); for (Tire t : tires.values()) { result.add(t.clone()); } return result; } @Override public int hashCode() { return id.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Car other = (Car) obj; return id.equals(other.id); } @Override public Car clone() { try { Car result = (Car) super.clone(); Map<Position, Tire> old = result.tires; result.tires = new HashMap<Position, Tire>(); Set<Entry<Position, Tire>> entrySet = old.entrySet(); for (Entry<Position, Tire> entry : entrySet) { result.tires.put(entry.getKey(), entry.getValue().clone()); } return result; } catch (CloneNotSupportedException e) { throw new Error(e); } } }
// Tireãã¼ã«ã«ã¨ã³ãã£ã㣠public class Tire implements Cloneable { private final Position localId; // ãã¼ã«ã«ãªåä¸æ§ãä¿è¨¼ããèå¥å private String name; public Tire(Position localId) { this.localId = localId; } public Position getLocalId() { return localId; } @Override public int hashCode() { return localId.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Tire other = (Tire) obj; return localId.equals(other.localId); } @Override public Tire clone() { try { return (Tire) super.clone(); } catch (CloneNotSupportedException e) { throw new Error(e); } } public void setName(String name) { this.name = name; } public String getName() { return name; } }
ããã¦ãã°ãã¼ãã«ãªã¨ã³ãã£ãã£(éç´ã«ã¼ã)ã¯ããããVMã®ã©ã¤ããµã¤ã¯ã«ãè¶
ãããåå¨ãªã®ã§ãæ°¸ç¶åããããã®æ段ã¨ãã¦ãªãã¸ããªã対å¿ã¥ãã®ãä¸è¬çããã¼ã«ã«ãªã¨ã³ãã£ãã£ãããªã¥ã¼ãªãã¸ã§ã¯ãã¯éç´ãããã®ã§ãã°ãã¼ãã«ãªã¨ã³ãã£ãã£ã«å¯¾ãããªãã¸ããªãæ°¸ç¶åããã¯ãã
ã¢ã°ãªã²ã¼ãã§ã©ã¤ããµã¤ã¯ã«ã管çããå¢çã¨ãã¦æ±ããã¾ãããªã®ã§ããªãã¸ããªã§æ°¸ç¶åãããã¨ãã¯ã²ã¨ã¾ã¨ãã§æ±ãããããã©ã³ã¶ã¯ã·ã§ã³ããã®åä½ã§ãªããã°ãªãã¾ããã
employeeRepository.store(employee); // nameãdepartmentãä¸ç·ã«æ°¸ç¶åããã carRepository.store(car); // tiresãä¸ç·ã«æ°¸ç¶åããã
éç´ã«ã¼ããéç´ã«ã¼ãã®åç §ãæã¤å ´å
éç´ã«ã¼ã(ã°ãã¼ãã«ãªã¨ã³ãã£ãã£)ãéç´ã«ã¼ã(ã°ãã¼ãã«ãªã¨ã³ãã£ãã£)ã®åç
§ãä¿æãã¦ããå ´åããçµæ§ãããããã®ããªã¨ã
æåã¯ãããªã³ã¼ããæãã¤ãã¾ããããéç´ã«ã¼ããéç´ã«ã¼ãã®åç
§ãä¿æãã¦ãããã¨ã¯ã親åé¢ä¿ã®åç
§ã¨ã¯ãããéãæ°ããã¾ããè¤è£½ã¯ãåä¾ã®ãã¨ã¯ã親ãã¡ããã¨ä¸å¤æ¡ä»¶ãã¿ã¦ããã¾ãããã¨ããæå³åããããã¨æãã®ã§ããã ããããã®å ´åã¯è¤è£½ã¯ãããªãã®ã§ã¯ï¼ã¨
public class Car implements Cloneable { private final String id; // ã°ãã¼ãã«ãªåä¸æ§ãä¿è¨¼ããèå¥å private Map<Position, Tire> tires = new HashMap<Position, Tire>(); private Engine engine; public Car(String id, Engine engine) { Validate.notNull(id); Validate.notNull(engine); this.id = id; setEngine(engine); // è¤è£½ } public String getId() { return id; } public void addTire(Tire tire) { tires.put(tire.getLocalId(), tire); } public Set<Tire> getTires() { Set<Tire> result = new HashSet<Tire>(); for (Tire t : tires.values()) { result.add(t.clone()); } return result; } // hashCode, equalsã¡ã½ããã¯çç¥ @Override public Car clone() { try { Car result = (Car) super.clone(); result.engine = engine.clone(); // è¤è£½ Map<Position, Tire> old = result.tires; result.tires = new HashMap<Position, Tire>(); Set<Entry<Position, Tire>> entrySet = old.entrySet(); for (Entry<Position, Tire> entry : entrySet) { result.tires.put(entry.getKey(), entry.getValue().clone()); } return result; } catch (CloneNotSupportedException e) { throw new Error(e); } } public void setEngine(Engine engine) { this.engine = engine.clone(); // ä¸å¤æ¡ä»¶ãç¶æããããã«è¤è£½ãåãè¾¼ã } public Engine getEngine() { return engine.clone(); // ä¸å¤æ¡ä»¶ãç¶æããããã«è¤è£½ãè¿ã } }
éç´ã«ã¼ãã¯ã°ãã¼ãã«ãªåä¸æ§ãæã£ã¦ãã¦ãä¸å¤æ¡ä»¶ã¯éç´ã«ã¼ãåä½ã§ç®¡çãããã¹ãã¨èããããè¤è£½ããªãã¨ãã解éãªã®ãããããªãã¨æã£ãã
public class Car implements Cloneable { private final String id; // ã°ãã¼ãã«ãªåä¸æ§ãä¿è¨¼ããèå¥å private Map<Position, Tire> tires = new HashMap<Position, Tire>(); private Engine engine; // ããã¯éç´ã¨ã¯éã public Car(String id, Engine engine) { Validate.notNull(id); Validate.notNull(engine); this.id = id; this.engine = engine; // ãã®ã¾ã¾åç §ãæ㤠} public String getId() { return id; } public void addTire(Tire tire) { tires.put(tire.getLocalId(), tire); } public Set<Tire> getTires() { Set<Tire> result = new HashSet<Tire>(); for (Tire t : tires.values()) { result.add(t.clone()); } return result; } // hashCode, equalsã¡ã½ããã¯çç¥ @Override public Car clone() { try { Car result = (Car) super.clone(); Map<Position, Tire> old = result.tires; result.tires = new HashMap<Position, Tire>(); Set<Entry<Position, Tire>> entrySet = old.entrySet(); for (Entry<Position, Tire> entry : entrySet) { result.tires.put(entry.getKey(), entry.getValue().clone()); } return result; } catch (CloneNotSupportedException e) { throw new Error(e); } } public void setEngine(Engine engine) { this.engine = engine; } public Engine getEngine() { return engine; } }
ãã®å ´åã®ãªãã¸ããªã®è²¬åã¯ä»¥ä¸ã®ããã«ãªãã¨æãã¾ããCarRepositoryã¯engineãæ°¸ç¶åããªãã¯ããCarã®engineã¯æ°¸ç¶å対象å¤ã®transientçãªæ±ãããªã¨ã
CarRepository carRepository = new CarRepository(); carRepository.store(car); // Engineã®æ°¸ç¶åã¯è¡ããªã EngineRepository engineRepository = new EngineRepository(); engineRepository.store(car.getEngine()); // Engineã¯EngineRepositoryã§æ°¸ç¶åãã
ã¾ããããã ã¨ä»¥ä¸ã®ãããªã¨ã³ãã£ãã£ã®åç §ãåå¾ããæã«å°ã£ã¦ãã¾ãã®ã§ã
CarRepository carRepository = new CarRepository(); Car car = carRepository.resolve(製é çªå·); // engineã¯ã©ãããã®ãï¼
CarRepositoryå
é¨ã§ãEngineRepository.resolve(ã¨ã³ã¸ã³ã®è£½é çªå·) ã«å§è²ããã°ãããããããªãã
以ä¸ã¯ãªã³ã¡ã¢ãªã®ãªãã¸ããªãªã®ã§ãcars.put(car.getId(), car)ããã¨car.engineãä¸ç·ã«ä¿åããã¦ãã¾ãã®ã§ãããæ°¸ç¶å対象å¤ã¨èãã¦EngineRepositoryã«å§è²ããè¨è¨ã«ãã¦ã¿ã¾ãããCarã¨Engineã®é¢é£ãä¿åãããããengineIdsãæã£ã¦ãã¾ããããªãã¸ããªã¯ã¹ãã¼ããã«ãªãã§ããããªã¨æã£ã¦ãã¾ãã
public class CarRepository { private final EngineRepository engineRepository; private final Map<String, Car> cars = new HashMap<String, Car>(); private final Map<String, String> engineIds = new HashMap<String, String>(); // Carã¨Engineã®é¢é£ãä¿åããããã public CarRepository(EngineRepository engineRepository) { this.engineRepository = engineRepository; } public Car resolve(String id) { Engine engine = engineRepository.resolve(engineIds.get(id)); // Engineãèªã¿è¾¼ã Car car = cars.get(id).clone(); // DBãªãããã§selectãã¦ãnew Car(id, engine)ã®ãããªã³ã¼ãã«ãªãã car.setEngine(engine); // car.engineã«è¨å®ãã return car; } public void store(Car car) { cars.put(car.getId(), car.clone()); // car.engineã¯ä¿åãããªãã¨ããåæãDBãªãDaoã«insert or update engineIds.put(car.getId(), car.getEngine().getId()); // é¢é£ãä¿å engineRepository.store(car.getEngine()); // EngineRepositoryã«car.engineã®æ°¸ç¶åãå§è² } // ä»ã®ã¡ã½ããã¯çç¥ }
DDDã¨ãã¦ã©ããã風ã«èããã®ãæ£ããããªããé£ããã§ãããã¨ããæãã§èãã¦ã¿ã¾ãããä»ã«ããæ¡ããã°æè¿ã
並è¡å¦çç°å¢ã§èããã¨ãCarã¯Engineã®ä¸å¤æ¡ä»¶ãèæ ®ããªãã®ã§ãã¹ã¬ããã»ã¼ãã«ããã«ã¯è¥å¹²æ³¨æãå¿ è¦ãªå´é¢ãããã¾ããããã¶ããã²ã¨ã¤ã®ã¹ã¬ããã®åä½ã§åã¨ã³ãã£ãã£ãç¬ç«ããã¤ã³ã¹ã¿ã³ã¹ã§ããã°ã¹ã¬ããã»ã¼ãã«ã§ããã¯ããªã®ã§ã次åã¯ãã®ç¹ãèãã¦ã¿ã¾ãã
追è¨ï¼
Car#cloneã§Tireãæ´é²ãã¦ããã®ã§ä¿®æ£ã