ที่เก็บ DDD ในเซอร์วิสแอ็พพลิเคชันหรือโดเมน


29

ฉันกำลังศึกษา DDD ในวันนี้และฉันมีคำถามบางอย่างเกี่ยวกับวิธีจัดการที่เก็บด้วย DDD

อันที่จริงฉันได้พบกับสอง possibilies:

คนแรก

วิธีแรกในการจัดการบริการที่ฉันอ่านคือการฉีดที่เก็บและรูปแบบโดเมนในบริการแอปพลิเคชัน

ด้วยวิธีนี้หนึ่งในวิธีการบริการแอปพลิเคชันเราเรียกวิธีการบริการโดเมน (การตรวจสอบกฎธุรกิจ) และถ้าเงื่อนไขเป็นสิ่งที่ดีพื้นที่เก็บข้อมูลจะถูกเรียกในวิธีพิเศษเพื่อยืนยัน / ดึงเอนทิตีจากฐานข้อมูล

วิธีง่ายๆในการทำสิ่งนี้อาจเป็น:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

อันที่สอง

ความเป็นไปได้ที่สองคือการฉีดที่เก็บภายใน domainService แทนและเพื่อใช้ที่เก็บผ่านบริการโดเมนเท่านั้น:

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

จากนี้ไปฉันไม่สามารถแยกแยะว่าอันไหนดีที่สุด (ถ้ามีอันที่ดีที่สุด) หรือสิ่งที่พวกเขาบ่งบอกถึงทั้งในบริบทของพวกเขา

คุณสามารถยกตัวอย่างให้ฉันดูได้จากที่หนึ่งอาจดีกว่าอันอื่นและทำไม



"เพื่อฉีดที่เก็บและโมเดลโดเมนในบริการแอปพลิเคชัน" คุณหมายความว่าอย่างไรโดยการฉีด "รูปแบบโดเมน" ที่ไหนสักแห่ง? AFAICT ในแง่ของรูปแบบโดเมน DDD หมายถึงชุดแนวคิดทั้งหมดจากโดเมนและการโต้ตอบระหว่างกันกับแนวคิดที่เกี่ยวข้องกับแอปพลิเคชัน มันเป็นสิ่งที่เป็นนามธรรมไม่ใช่วัตถุในหน่วยความจำ คุณไม่สามารถฉีดได้
Alexey

คำตอบ:


31

คำตอบสั้น ๆ คือ - คุณสามารถใช้ที่เก็บข้อมูลจากบริการแอปพลิเคชันหรือบริการโดเมน - แต่สิ่งสำคัญคือต้องพิจารณาสาเหตุและวิธีการที่คุณทำเช่นนั้น

วัตถุประสงค์ของการบริการโดเมน

บริการโดเมนควรสรุปแนวคิด / ตรรกะของโดเมน - ดังนั้นวิธีการบริการโดเมน:

domainService.persist(data)

ไม่ได้อยู่ในบริการโดเมนเนื่องจากpersistไม่ใช่ส่วนหนึ่งของภาษาที่แพร่หลายและการดำเนินการเก็บรักษาไม่ได้เป็นส่วนหนึ่งของตรรกะทางธุรกิจของโดเมน

โดยทั่วไปบริการโดเมนจะมีประโยชน์เมื่อคุณมีกฎ / ตรรกะทางธุรกิจที่ต้องการการประสานงานหรือทำงานกับการรวมมากกว่าหนึ่ง หากตรรกะนั้นเกี่ยวข้องกับการรวมเพียงหนึ่งเดียวก็ควรอยู่ในวิธีการของเอนทิตีของมวลรวมนั้น

ที่เก็บใน Application Services

ดังนั้นในแง่ที่ว่าในตัวอย่างของคุณผมชอบตัวเลือกแรกของคุณ - แต่แม้จะมีมีห้องพักสำหรับการปรับปรุงเป็นบริการโดเมนของคุณจะรับข้อมูลดิบจาก API - ทำไมควรบริการโดเมนรู้เกี่ยวกับโครงสร้างของdata? นอกจากนี้ข้อมูลดูเหมือนจะเกี่ยวข้องกับการรวมกลุ่มเดียวเท่านั้นดังนั้นจึงมีค่า จำกัด ในการใช้บริการโดเมนสำหรับการนั้น - โดยทั่วไปฉันจะใส่การตรวจสอบภายในตัวสร้างเอนทิตี เช่น

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

และโยนข้อยกเว้นถ้ามันไม่ถูกต้อง ขึ้นอยู่กับเฟรมเวิร์กแอปพลิเคชันของคุณอาจเป็นเรื่องง่ายที่จะมีกลไกที่สอดคล้องกันสำหรับการตรวจจับข้อยกเว้นและจับคู่กับการตอบสนองที่เหมาะสมสำหรับประเภท API - เช่น REST api ส่งคืนรหัสสถานะ 400

ที่เก็บในบริการโดเมน

แม้ว่าข้างต้นบางครั้งจะเป็นประโยชน์ในการแทรกและใช้พื้นที่เก็บข้อมูลในบริการโดเมน แต่เฉพาะในกรณีที่ที่เก็บข้อมูลของคุณถูกนำไปใช้เช่นที่พวกเขายอมรับและส่งกลับรากรวมรวมเท่านั้นและยังเป็นที่ที่คุณสรุปตรรกะที่เกี่ยวข้อง เช่น

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

การใช้บริการโดเมนจะมีลักษณะดังนี้:

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

ข้อสรุป

กุญแจสำคัญที่นี่คือบริการโดเมนencapsulateกระบวนการที่เป็นส่วนหนึ่งของภาษาที่แพร่หลาย ในการที่จะบรรลุบทบาทของตนนั้นจำเป็นต้องใช้ที่เก็บข้อมูล - และมันก็ดีมากที่จะทำเช่นนั้น

แต่การเพิ่มบริการโดเมนที่ล้อมที่เก็บด้วยวิธีการที่เรียกว่าpersistเพิ่มมูลค่าน้อย

บนพื้นฐานดังกล่าวหากบริการแอปพลิเคชันของคุณแสดงกรณีใช้งานที่เรียกร้องให้ทำงานกับการรวมเพียงครั้งเดียวจะไม่มีปัญหาในการใช้ที่เก็บโดยตรงจากบริการแอปพลิเคชัน


ตกลงดังนั้นถ้าฉันได้กฎทางธุรกิจ (การยอมรับกฎรูปแบบสเปค) ถ้ามันเกี่ยวข้องกับเอนทิตีเดียวฉันควร แต่การตรวจสอบในเอนทิตีนั้น ดูเหมือนว่าแปลกที่จะใช้กฎเกณฑ์ทางธุรกิจเช่นการควบคุมรูปแบบอีเมลผู้ใช้ที่ดีภายในองค์กรผู้ใช้ ไม่ใช่เหรอ เกี่ยวกับการตอบสนองทั่วโลกขอขอบคุณ มันได้ว่าไม่มี "กฎเริ่มต้นที่จะใช้" และขึ้นอยู่กับการใช้งานของเรา ฉันมีงานที่ต้องทำเพื่อแยกความแตกต่างของงานนี้ทั้งหมด
mfrachet

2
ในการชี้แจงกฎที่เป็นของกิจการนั้นเป็นเพียงกฎที่เป็นความรับผิดชอบของหน่วยงานนั้น ฉันเห็นด้วยการควบคุมรูปแบบอีเมลผู้ใช้ที่ดีไม่รู้สึกว่าเป็นของผู้ใช้เอนทิตี โดยส่วนตัวแล้วฉันชอบที่จะใส่กฎการตรวจสอบเช่นนั้นไว้ใน Value Object ที่แสดงที่อยู่อีเมล ผู้ใช้จะมีคุณสมบัติประเภท EmailAddress และตัวสร้าง EmailAddress ยอมรับสตริงและส่งข้อยกเว้นถ้าสตริงไม่ตรงกับรูปแบบที่ต้องการ จากนั้นคุณสามารถใช้ EmailAddress ValueObject อีกครั้งบนเอนทิตีอื่นที่จำเป็นต้องจัดเก็บที่อยู่อีเมล
Chris Simon

ตกลงฉันเห็นเหตุผลที่จะใช้ Value Object ตอนนี้ แต่หมายความว่าวัตถุค่าเป็นคุณสมบัติที่เป็นกฎธุรกิจที่จัดการรูปแบบหรือไม่
mfrachet

1
วัตถุค่าควรไม่เปลี่ยนรูป โดยทั่วไปนี่หมายความว่าคุณเริ่มต้นและตรวจสอบความถูกต้องในตัวสร้างและสำหรับคุณสมบัติใด ๆ ให้ใช้ชุดรูปแบบสาธารณะ get / private แต่คุณสามารถใช้การสร้างภาษาเพื่อกำหนดความเท่าเทียมกันกระบวนการ ToString ฯลฯ เช่นkacper.gunia.me/ddd-building-blocks-in-php-value-objectหรือgithub.com/spring-gjectfiregexfire-examples/ blob / master / …
Chris Simon

ขอบคุณ @ChrisSimon ในที่สุดและตอบสถานการณ์ DDD ในชีวิตจริงที่เกี่ยวข้องกับรหัสไม่ใช่แค่ทฤษฎี ฉันใช้เวลา 5 วันในการสืบค้น SO และเว็บเพื่อเป็นตัวอย่างของการสร้างและบันทึกการรวมและนี่คือคำอธิบายที่ชัดเจนที่สุดที่ฉันพบ
e_i_pi

2

มีปัญหากับคำตอบที่ยอมรับได้:

รูปแบบโดเมนไม่ได้รับอนุญาตให้ขึ้นอยู่กับพื้นที่เก็บข้อมูลและบริการโดเมนเป็นส่วนหนึ่งของรูปแบบโดเมน -> บริการโดเมนไม่ควรขึ้นอยู่กับพื้นที่เก็บข้อมูล

สิ่งที่คุณควรทำคือการรวบรวมเอนทิตีทั้งหมดของคุณที่จำเป็นสำหรับการดำเนินการทางตรรกะทางธุรกิจที่มีอยู่แล้วในบริการแอพพลิเคชั่น

จากตัวอย่างของคุณอาจมีลักษณะเช่นนี้:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

ดังนั้นกฎง่ายๆ: รุ่นโดเมนไม่ได้ขึ้นอยู่กับเลเยอร์ด้านนอก

Application vs Domain service จากบทความนี้ :

  • บริการโดเมนนั้นละเอียดมากเพราะแอปพลิเคชันบริการเป็นส่วนที่มีจุดประสงค์ในการให้บริการ API

  • บริการโดเมนมีตรรกะของโดเมนที่ไม่สามารถวางในเอนทิตีหรือวัตถุค่าตามธรรมชาติในขณะที่แอปพลิเคชันบริการจัดการการดำเนินการของตรรกะโดเมนและตัวเองไม่ใช้ตรรกะโดเมนใด ๆ

  • วิธีการบริการโดเมนสามารถมีองค์ประกอบโดเมนอื่น ๆ เป็นตัวถูกดำเนินการและคืนค่าในขณะที่บริการแอปพลิเคชันดำเนินการตามตัวถูกดำเนินการเล็กน้อยเช่นค่าตัวตนและโครงสร้างข้อมูลดั้งเดิม

  • บริการแอปพลิเคชันประกาศการพึ่งพาบริการโครงสร้างพื้นฐานที่จำเป็นในการดำเนินการตรรกะของโดเมน


1

ทั้งสองรูปแบบของคุณนั้นดียกเว้นว่าบริการและวัตถุของคุณนั้นมีความรับผิดชอบที่สอดคล้องกัน

ก่อนอื่นให้พูดว่าวัตถุโดเมนของคุณคืออะไรและพูดคุยเกี่ยวกับสิ่งที่สามารถทำได้ภายในภาษาของโดเมน ถ้ามันถูกต้องหรือไม่ถูกต้องทำไมไม่มีสิ่งนี้เป็นคุณสมบัติของวัตถุโดเมน?

ถ้าเช่นความถูกต้องของวัตถุนั้นสมเหตุสมผลในแง่ของวัตถุอื่นดังนั้นคุณอาจมี 'กฎการตรวจสอบความถูกต้อง X สำหรับวัตถุโดเมน' ซึ่ง can.be ถูกห่อหุ้มในชุดของบริการ

การตรวจสอบวัตถุจำเป็นต้องเก็บไว้ในกฎเกณฑ์ทางธุรกิจของคุณหรือไม่? อาจจะไม่. ความรับผิดชอบ 'การจัดเก็บวัตถุ' ตามปกติจะไปในวัตถุที่เก็บแยกต่างหาก

ตอนนี้คุณมีการดำเนินการที่คุณต้องการดำเนินการซึ่งครอบคลุมช่วงความรับผิดชอบสร้างวัตถุตรวจสอบความถูกต้องและถ้าถูกต้องเก็บไว้

การดำเนินการนี้อยู่ภายในวัตถุโดเมนหรือไม่ จากนั้นทำให้มันเป็นส่วนหนึ่งของวัตถุนั่นคือExamQuestion.Answer(string answer)

เหมาะสมกับส่วนอื่น ๆ ของโดเมนของคุณหรือไม่ วางไว้ตรงนั้นBasket.Purchase(Order order)

คุณต้องการทำบริการ ADM REST หรือไม่ โอเคถ้าอย่างนั้น.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.