Kho DDD trong ứng dụng hoặc dịch vụ miền


29

Tôi đang nghiên cứu DDD những ngày này, và tôi có một số câu hỏi liên quan đến cách quản lý kho lưu trữ với DDD.

Thật ra, tôi đã gặp hai sở hữu:

Đầu tiên

Cách quản lý dịch vụ đầu tiên tôi đọc là tiêm một kho lưu trữ và mô hình miền trong một dịch vụ ứng dụng.

Theo cách này, trong một trong các phương thức dịch vụ ứng dụng, chúng tôi gọi một phương thức dịch vụ miền (kiểm tra các quy tắc nghiệp vụ) và nếu điều kiện tốt, kho lưu trữ được gọi trên một phương thức đặc biệt để duy trì / truy xuất thực thể từ cơ sở dữ liệu.

Một cách đơn giản để làm điều này có thể là:

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))
    }
    // ...
  }

}

Cái thứ hai

Khả năng thứ hai là tiêm kho lưu trữ bên trong domainService và chỉ sử dụng kho lưu trữ thông qua dịch vụ miền:

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
  }

}

Từ bây giờ, tôi không thể phân biệt cái nào là tốt nhất (nếu có cái tốt nhất) hoặc cái mà chúng ám chỉ cả trong bối cảnh của chúng.

Bạn có thể cung cấp cho tôi ví dụ nơi một người có thể tốt hơn người khác và tại sao?



"để tiêm một kho lưu trữ và một mô hình miền trong một dịch vụ ứng dụng." Bạn có ý nghĩa gì khi tiêm một "mô hình miền" ở đâu đó? AFAICT về mặt mô hình miền DDD có nghĩa là toàn bộ các khái niệm từ miền và các tương tác giữa chúng có liên quan đến ứng dụng. Đó là một điều trừu tượng, nó không phải là một đối tượng trong bộ nhớ. Bạn không thể tiêm nó.
Alexey

Câu trả lời:


31

Câu trả lời ngắn gọn là - bạn có thể sử dụng các kho lưu trữ từ một dịch vụ ứng dụng hoặc dịch vụ tên miền - nhưng điều quan trọng là phải xem xét lý do tại sao và làm thế nào, bạn đang làm như vậy.

Mục đích của dịch vụ tên miền

Dịch vụ miền cần gói gọn các khái niệm / logic miền - như vậy, phương thức dịch vụ miền:

domainService.persist(data)

không thuộc về một dịch vụ tên miền, vì persistnó không phải là một phần của ngôn ngữ phổ biến và hoạt động của sự kiên trì không phải là một phần của logic kinh doanh tên miền.

Nói chung, các dịch vụ miền rất hữu ích khi bạn có các quy tắc / logic kinh doanh đòi hỏi phải phối hợp hoặc làm việc với nhiều hơn một tổng hợp. Nếu logic chỉ liên quan đến một tổng hợp, thì nó phải nằm trong một phương thức trên các thực thể của tổng hợp đó.

Các kho lưu trữ trong Dịch vụ Ứng dụng

Vì vậy, theo nghĩa đó, trong ví dụ của bạn, tôi thích tùy chọn đầu tiên của bạn - nhưng thậm chí còn có chỗ để cải thiện, vì dịch vụ miền của bạn đang chấp nhận dữ liệu thô từ api - tại sao dịch vụ tên miền phải biết về cấu trúc của data? Ngoài ra, dữ liệu dường như chỉ liên quan đến một tổng hợp duy nhất, do đó, có giá trị hạn chế trong việc sử dụng dịch vụ miền cho điều đó - nói chung tôi sẽ đặt xác thực bên trong hàm tạo thực thể. ví dụ

postAction(data){

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

  this.repository.persist(entity);

  // ...
}

và ném một ngoại lệ nếu nó không hợp lệ. Tùy thuộc vào khung ứng dụng của bạn, có thể đơn giản để có một cơ chế nhất quán để bắt ngoại lệ và ánh xạ nó tới phản hồi thích hợp cho loại api - ví dụ: đối với api REST, trả về 400 mã trạng thái.

Các kho lưu trữ trong dịch vụ miền

Mặc dù ở trên, đôi khi rất hữu ích khi tiêm và sử dụng kho lưu trữ trong dịch vụ tên miền, nhưng chỉ khi kho của bạn được triển khai sao cho chúng chỉ chấp nhận và trả về gốc tổng hợp, và cũng là nơi bạn đang trừu tượng hóa logic liên quan đến nhiều tổng hợp. ví dụ

postAction(data){

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

  // ...
}

việc triển khai dịch vụ tên miền sẽ như sau:

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);

}

Phần kết luận

Chìa khóa ở đây là dịch vụ tên miền gói gọn một quy trình là một phần của ngôn ngữ có mặt khắp nơi. Để hoàn thành vai trò của mình, nó cần sử dụng các kho lưu trữ - và hoàn toàn ổn để làm điều đó.

Nhưng việc thêm một dịch vụ miền bao bọc một kho lưu trữ bằng một phương thức gọi là persistthêm ít giá trị.

Trên cơ sở đó, nếu dịch vụ ứng dụng của bạn thể hiện trường hợp sử dụng chỉ yêu cầu làm việc với một tổng hợp duy nhất, thì không có vấn đề gì khi sử dụng kho lưu trữ trực tiếp từ dịch vụ ứng dụng.


Được rồi, vì vậy nếu tôi có quy tắc kinh doanh (thừa nhận quy tắc Mẫu đặc tả), nếu nó chỉ liên quan đến một thực thể, tôi có nên xác thực trong thực thể đó không? Có vẻ kỳ lạ khi tiêm các quy tắc kinh doanh như kiểm soát định dạng thư người dùng tốt bên trong thực thể người dùng. Phải không? Liên quan đến các phản ứng toàn cầu, cảm ơn bạn. Nó đã nhận được rằng không có "quy tắc mặc định để áp dụng", và nó thực sự phụ thuộc vào các giai đoạn của chúng tôi. Tôi có một số việc phải làm để phân biệt rõ tất cả công việc này
mfrachet

2
Để làm rõ, các quy tắc thuộc về thực thể chỉ là các quy tắc là trách nhiệm của thực thể đó. Tôi đồng ý, việc kiểm soát một định dạng email người dùng tốt không cảm thấy như nó thuộc về thực thể Người dùng. Cá nhân, tôi muốn đặt các quy tắc xác thực như thế vào Đối tượng Giá trị đại diện cho một địa chỉ email. Người dùng sẽ có một thuộc tính loại EmailAddress và hàm tạo EmailAddress chấp nhận một chuỗi và ném một ngoại lệ nếu chuỗi không khớp với định dạng được yêu cầu. Sau đó, bạn có thể sử dụng lại EmailAddress ValueObject trên các thực thể khác cần lưu trữ địa chỉ email.
Chris Simon

Được rồi tôi hiểu tại sao nên sử dụng Value Object ngay bây giờ. Nhưng nó có nghĩa là đối tượng giá trị có một thuộc tính là quy tắc kinh doanh quản lý định dạng?
mfrachet

1
Đối tượng giá trị nên bất biến. Nói chung, điều này có nghĩa là bạn khởi tạo và xác thực trong hàm tạo và đối với bất kỳ thuộc tính nào, hãy sử dụng mẫu thiết lập công khai / riêng tư. Nhưng bạn có thể sử dụng các cấu trúc ngôn ngữ để xác định sự bình đẳng, tiến trình ToString vv ví dụ kacper.gunia.me/ddd-building-blocks-in-php-value-object hoặc github.com/spring-projects/spring-gemfire-examples/ blob / master /
Chris Simon

Cảm ơn bạn @ChrisSimon, cuối cùng và trả lời cho một tình huống DDD ngoài đời thực liên quan đến mã chứ không chỉ là lý thuyết. Tôi đã dành 5 ngày để truy tìm SO và web cho một ví dụ chức năng về việc tạo và lưu tổng hợp, và đây là lời giải thích rõ ràng nhất mà tôi đã tìm thấy.
e_i_pi

2

Có vấn đề với câu trả lời được chấp nhận:

Mô hình miền không được phép phụ thuộc vào kho lưu trữ và dịch vụ miền là một phần của mô hình miền -> dịch vụ miền không nên phụ thuộc vào kho lưu trữ.

Thay vào đó, những gì bạn nên làm là tập hợp tất cả các thực thể cần thiết để thực thi logic nghiệp vụ đã có trong dịch vụ ứng dụng và sau đó chỉ cung cấp cho các mô hình của bạn các đối tượng được khởi tạo.

Dựa trên ví dụ của bạn, nó có thể trông như thế này:

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))
    }
    // ...
  }
}

Vì vậy, quy tắc ngón tay cái: Mô hình miền không phụ thuộc vào các lớp bên ngoài

Ứng dụng và dịch vụ tên miền Từ bài viết này :

  • Các dịch vụ tên miền rất chi tiết trong đó các dịch vụ ứng dụng là một mặt tiền có mục đích cung cấp API.

  • Các dịch vụ miền chứa logic miền không thể được đặt tự nhiên trong một đối tượng thực thể hoặc giá trị trong khi các dịch vụ ứng dụng phối hợp thực thi logic miền và bản thân chúng không thực hiện bất kỳ logic miền nào.

  • Các phương thức dịch vụ miền có thể có các thành phần miền khác là toán hạng và trả về giá trị trong khi các dịch vụ ứng dụng hoạt động dựa trên các toán hạng tầm thường như giá trị nhận dạng và cấu trúc dữ liệu nguyên thủy.

  • Các dịch vụ ứng dụng khai báo sự phụ thuộc vào các dịch vụ cơ sở hạ tầng cần thiết để thực thi logic miền.


1

Cả hai mô hình của bạn đều tốt trừ khi các dịch vụ và đối tượng của bạn gói gọn một số trách nhiệm nhất quán.

Trước hết hãy nói đối tượng miền của bạn là gì và nói về những gì nó có thể làm trong ngôn ngữ miền. Nếu nó có thể hợp lệ hoặc không hợp lệ tại sao không có điều này như một tài sản của chính đối tượng miền?

Ví dụ, nếu tính hợp lệ của các đối tượng chỉ có ý nghĩa về mặt đối tượng khác thì có thể bạn phải chịu trách nhiệm 'quy tắc xác thực X cho các đối tượng miền', có thể được gói gọn trong một tập hợp các dịch vụ.

Có xác nhận một đối tượng bắt buộc phải lưu trữ nó trong các quy tắc kinh doanh của bạn? Chắc là không. Trách nhiệm 'lưu trữ đối tượng' thường đi trong một đối tượng lưu trữ riêng biệt.

Bây giờ bạn có một hoạt động bạn muốn thực hiện bao gồm một loạt các trách nhiệm, tạo một đối tượng, xác nhận nó và nếu hợp lệ, lưu trữ nó.

Là hoạt động này nội tại đối tượng miền? Sau đó biến nó thành một phần của đối tượng đóExamQuestion.Answer(string answer)

Có phù hợp với một số phần khác của tên miền của bạn? đặt nó ở đóBasket.Purchase(Order order)

Bạn có muốn làm dịch vụ ADM REST không? Được rồi

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.