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.
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
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