5 Ekim 2020 Pazartesi

Domain Driven Design - Anemic Domain Model

Giriş
Bu konuyu ilk olarak burada gördüm. Bu konuda vurgulanan şey Bounded Context içindeki modelin hiç bir logic içermemesi. Normalde beklenen şey şöyle.
The Domain Model should be useful for the problem we're trying to solve. The model is there to enforce the business rules that we have ...
Örnek
Elimizde şöyle bir Spring servisi olsun.
@Service
public class ExpenseService {
  private ExpenseRepo expenseRepo;
  private AuthenticationBO authenticationBO;
  ...
  // other injected classes

  @Override
  @Transactional
  public void createExpense(ExpenseDTO expenseDTO) throws ValidationException {
    validateExpense(expenseDTO);
    ExpenseEntity expenseEntity = new ExpenseEntity();
    initExpenseEntity(expenseEntity, expenseDTO);
    expenseRepo.save(expenseEntity);
  }

  ...
  // other methods
  private void initExpenseEntity(ExpenseEntity expenseEntity, ExpenseDTO expenseDTO) {
    UserEntity userEntity = authenticationBO.getLoggedUser();
    expenseEntity.setUser(userEntity);
    expenseEntity.setPrice(expenseDTO.getPrice());
    expenseEntity.setComment(expenseDTO.getComment());
    expenseEntity.setDate(LocalDateTime.now());
    initExpenseTypes(expenseEntity, expenseDTO.getTypes());
  }
  ...
  // other methods
}
Bu kodda domain nesnesi sadece getter/setter'lardan oluştuğu için anemic kabul ediliyor. Zararı şöyle
Official Spring tutorials teach us that domain objects shouldn’t have any methods except getters and setters and they should be POJOs. Many authors (like Martin Fowler) consider it an antipattern and call it the Anemic Domain Model.

With Anemic Domain Design, all a program’s logic is kept in the business logic layer (classes with Service or BO suffixes). Then domain objects don’t have methods that operate with class fields, hence the object doesn’t have behavior. That breaks OOP principles, GRASP patterns, and prevent us from implementing design patterns.
Bu kodu zengin alan modeli (rich domain model) kapsamında şu hale getirebiliriz. Bu sefer de @Entity nesnesi içine Repository eklemek zorunda kaldık. Bence bu da iyi değil
@Entity
public class ExpenseEntity {
  ...
  @Autowired // model has dependency from other layers and from infrastructure
  private ExpenseRepo expenseRepo;
  @Autowired
  private AuthenticationBO authenticationBO;
  @Autowired
  private UserRepo userRepo;
  ...
  @Transactional
  public void createExpense(ExpenseDTO expenseDTO) {
    // model does too much!
    validateExpense(expenseDTO);
    ExpenseEntity expenseEntity = new ExpenseEntity();
    initExpenseEntity(expenseEntity, expenseDTO);
    expenseRepo.save(expenseEntity);
  }
  private void initExpenseEntity(ExpenseEntity expenseEntity, ExpenseDTO expenseDTO) {
    UserEntity userEntity = authenticationBO.getLoggedUser();
    expenseEntity.setUser(userEntity);
    ...
    initExpenseTypes(expenseEntity, expenseDTO.getTypes());
  }
  private void initExpenseTypes(ExpenseEntity expenseEntity, List<String> types) {
    // model persists to a DB other objects!
    List<ExpenseTypeDictEntity> expenseTypes = new ArrayList<>();
    types.forEach(x -> {
      ExpenseTypeDictEntity typeDict = expenseTypeDictRepo
        .findByNameIgnoreCase(x.trim())
        .orElseGet(() -> createExpenseTypeDict(x));
       ...
    });
    expenseEntity.setExpenseTypeDict(expenseTypes);
  }
  private ExpenseTypeDictEntity createExpenseTypeDict(String name) {...}
}
Bu kodun şu akışa uygun hale gelmesi gerekir.
Böylece alan nesneleri ile JPA teknolojisi birbirinden ayrılabilir. Yani basit bir kural olarak alan modeli teknolojiden bağımsız olmalıdır. Açıklaması şöyle.
The business logic pertaining to the Product class can exist within the class itself and the business logic for the domain can be tested in isolation. The Product class contains data fields and these can be persisted to a database. As part of that process the data fields are first wrapped in another class, and then transformed into a JPA entity before being persisted. 
Örnek
Yine sadece Spring servisleri kullanan bir başka örnek şöyle
@Transactional
public void clearBills(Long customerId) {
  // Get bills needed for clearing
  ClearContext context = getClearContext(customerId);
  // Verify that the amount is legal
  checkAmount(context);
  // Determine whether coupons are available and return the deductible amount
  CouponDeductibleResponse deductibleResponse = couponDeducted(context);
  // clear all bills
  DepositClearResponse response = clearBills(context);
  // Send repayment reconciliation message
  repaymentService.sendVerifyBillMessage(customerId, context.getDeposit());
  // Update account balance
  accountService.clear(context, response);
  // Dealing with cleared coupons, used up or unbound
  couponService.clear(deductibleResponse);
  // Save coupon deduction records
  clearCouponDeductService.add(context, deductibleResponse);
}
Örnek - Anemic Mesajlaşma Sistemi
Bir mesajlaşma yazılımını güncellerken şöyle bir durumla karşılaştım. Güncellediğimiz yazılım, mesajlar arasında graph tarzı ilişkiler tutuyordu. Mesajlar ara katman yazılımından gelen ve behaviour içermeyen, sadece veri içeren nesnelerdi.

Mesajları çeşitli durumlara göre sıralayan (örneğin öncelik) bir servis katmanı da yoktu.

Dolayısıyla domain sadece getter/setter'lardan oluşan data nesnelerinin graph'ından ibaretti.

Örnek - Anemic İstek (Request) Sistemi
Bir  yazılım, çeşitli istekler gönderiyordu. Bu istekler bir ICD mesajında tanımlıydı. Ancak istekleri sarmalayan bir domain nesnesi yoktu. Gönderilen istekler farklı listelerde saklanıyordu. Mesela sentList, acceptedList, deniedList gibi. Gelen cevaba göre istek farklı listeye taşınıyordu. Bir zaman sonra farklı bir liste daha eklenmesi gerekti. Bu liste notHitList yani işlenen ancak istenilen sonucun alınamadığı bir listeydi. Kod çorbaya döndü.

Domain anemic olmasaydı, Request nesnesi diye bir şey olurdu. Bu nesnenin status diye bir alanı olurdu. Bu alan tüm listelerin bir enum'u şeklinde olurdu. Yeni liste eklemek yerine yeni enum eklenir ve elimizde tek bir liste olurdu

Aslında her gönderilen istek bir Request içinde saklansa 

Hiç yorum yok:

Yorum Gönder