간단한 비즈니스 로직 개발
비즈니스 로직을 작성하는 방식에는 크게 두가지로 나뉜다.
트랜잭션 스크립트 방식과, 도메인 모델 패턴 방식이다.
- 트랜잭션 스크립트: 절차지향 스크립트 방식으로 구현한다.
- 도메인 모델 패턴: 도메인 객체(엔티티와 값 객체) 안에 로직을 캡슐화
예제를 위해 간단한 요구사항은 만들어봤다.
- 테넌트(Tenant)는 사용자(Invitee)에게 초대장을 보낼 수 있다.
- 사용자는 초대를 받으면 수락/거절을 선택할 수 있다.
- 초대 상태를 변경하는 것은 초대받은자만이 가능하다.
트랜잭션 스크립트 방식
먼저 트랜잭션 스크립트 방식을 알아보자
@Entity
class Invitation(
val tenantId: Long,
val inviteeId: Long,
val message: String = "",
var status: InvitationStatus = InvitationStatus.SENT,
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
)
@Transactional
@Service
class InvitationService(
private val repository: InvitationRepository,
) {
fun invite(tenantId: Long, inviteeId: Long) {
// 초대장 생성
}
fun accept(invitationId: Long, loginId: Long) {
val 초대장 = repository.findBy(invitationId)
check(초대장.status == InvitationStatus.SENT) { "현재 초대장은 상태를 변경할 수 없습니다. 초대장이 이미 처리되었거나 만료되었습니다." }
check(초대장.inviteeId == loginId) { "이 초대장은 해당 수신자만 상태를 변경할 수 있습니다." }
초대장.status = InvitationStatus.ACCEPT
repository.save(초대장)
}
}
비즈니스 로직이 별도의 객체 없이 서비스나 함수로 처리되는 방식이다.
데이터는 일반적으로 데이터 모델(Entity, DTO) 속성에 의존해 로직을 작성한다.
도메인 모델 패턴 방식
트랜잭션 스크립트 방식과는 다르게 Invitation 도메인 모델안에 비즈니스 로직을 담아냈다.
@Entity
class Invitation(
val tenantId: Long,
val inviteeId: Long,
val message: String = "",
var status: InvitationStatus = InvitationStatus.SENT,
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
) : AbstractAggregateRoot<Invitation>() {
fun accept(inviteeId: Long) {
check(this.inviteeId == inviteeId) { "이 초대장은 해당 수신자만 상태를 변경할 수 있습니다." }
check(status == InvitationStatus.SENT) { "현재 초대장은 상태를 변경할 수 없습니다. 초대장이 이미 처리되었거나 만료되었습니다." }
this.status = status
}
}
@Transactional
@Service
class InvitationService(
private val repository: InvitationRepository,
) {
fun invite(tenantId: Long, inviteeId: Long) {
// 초대장 생성
}
fun accept(invitationId: Long, loginId: Long) {
val 초대장 = repository.findBy(invitationId)
초대장.accept(loginId)
repository.save(초대장)
}
}
언제 사용할까?
트랜잭션 스크립트를 선호하는 경우
- 시스템이 단순한 CRUD 기반이고 도메인 규칙이 거의 없는 경우(배치, 통계)
- 초기 개발 속도가 중요하거나 변경 가능성이 높은 프로토타입 단계
도메인 모델을 선호하는 경우
- 도메인 규칙이 복잡하고 강하게 결합된 경우
- 시스템이 장기적으로 확장성과 유지보수가 필요할 경우
트랜잭션 스크립트 방식과 도메인 모델 패턴에 대해 살펴보았다.
앞서 설명했듯, 통계 처리나 배치 작업처럼 도메인 규칙이 거의 없고 절차 중심의 로직이 많은 시스템에서는 트랜잭션 스크립트 방식이 적합하다.
이 방식은 코드가 단순하고 빠르게 구현할 수 있어 초기 개발 속도가 중요한 경우 특히 유용하다.
반면, 도메인 규칙이 복잡하거나 데이터 변경 시 정합성이 중요한 시스템에서는 도메인 모델 패턴이 더 효과적이다.
도메인 모델 패턴을 적용하면 비즈니스 로직을 도메인 객체 내부에 캡슐화하여 데이터의 변경 책임을 도메인 모델 자체가 판단하도록 설계할 수 있다.
이를 통해 정합성을 유지할 수 있을 뿐만 아니라, 유지보수가 용이한 시스템 구조를 만들 수 있다.
또한 도메인 모델은 테스트의 관점에서도 강점이 있다.
도메인 객체 단위로 테스트가 가능하므로, Service 레이어나 Repository의 모킹 없이 독립적으로 도메인 로직을 검증할 수 있다.
이는 테스트 코드를 작성하는 시간을 단축시킨다.
결론적으로, 트랜잭션 스크립트 방식과 도메인 모델 패턴은 각각의 장단점과 적합한 사용 사례가 있으므로, 시스템의 복잡성, 유지보수 요구사항, 초기 개발 속도 등을 고려하여 상황에 맞게 선택하는 것이 중요하다.