DDD책을 정독해보고 아키텍처 패턴중에 하나인 포트와 어댑터
패턴을 잠깐 공부해본 적이있다. 흔히 말하는 클린아키텍처와 거의 같은말로 알려져있는데, 구현하는 방법에 대해 자세히 나와있는 만들면서 배우는 클린 아키텍처-톰 홈버그 지음
를 읽게 되었다. MSA를 도입하기 전에 이 방식을 통해 아키텍처를 구현해 보고 들어가보고자 한다.
"클린 아키텍처(Clean Architecture)"는 소프트웨어 개발에 사용되는 디자인 패턴 중 하나로, 로버트 C. 마틴(Robert C. Martin)이 제안한 아키텍처 원칙이다. 헥사고날 아키텍처라 부르기도하고 포트와 어댑터 패턴이라고 부르기도 한다.
web(Controller) + domain + persistence (repositoryImpl)으로 이루어진 가장 기본적이면서 간단한 구조이다.
하지만 3가지 정도 단점을 꼽을 수 있다.
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@Builder
public class User {
private final UserId id;
private final String email;
private final String password;
private final String nickname;
private final String name;
@Value
public static class UserId {
private final Long value;
}
}
public interface CreateUserUseCase {
CreateUserResponse createUser(CreateUserCommand command);
}
public interface CreateUserPort {
User createUser(User user);
}
@RequiredArgsConstructor
@UseCase
@Transactional
class CreateUserService implements CreateUserUseCase {
private final CreateUserPort createUserPort;
private final LoadUserPort loadUserPort;
private final PasswordEncoderPort encoderPort;
@Override
public CreateUserResponse createUser(CreateUserCommand command) {
User user = User.builder()
.email(command.getEmail())
.nickname(command.getNickname())
.name(command.getName())
.password(encoderPort.encode(command.getPassword()))
.build();
User createdUser = createUserPort.createUser(user);
return CreateUserResponse.builder()
.id(createdUser.getId().getValue())
.name(createdUser.getNickname())
.email(createdUser.getEmail())
.name(createdUser.getName())
.password(createdUser.getPassword())
.nickname(createdUser.getNickname())
.build();
}
}
@RequiredArgsConstructor
@PersistenceAdapter
class UserPersistenceAdapter implements CreateUserPort {
private final SpringDataUserRepository userRepository;
private final UserMapper userMapper;
@Override
public User createUser(User user) {
checkDuplication(user);
UserJpaEntity userJpaEntity = userMapper.mapToJpaEntity(user);
return userMapper.mapToDomainEntity(userRepository.save(userJpaEntity));
}
private void checkDuplication(User user){
userRepository.findByEmail(user.getEmail())
.ifPresent(o -> {throw new UserAlreadyExistsException();});
userRepository.findByNickname(user.getNickname())
.ifPresent(o -> {throw new UserAlreadyExistsException();});
}
}
@Entity
@Table(name = "users")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
class UserJpaEntity {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String nickname;
@Column(nullable = false)
private String name;
}
@WebAdapter
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
class UserController {
private final CreateUserUseCase createUserUseCase;
@PostMapping
public ApiResponse createUser(@RequestBody CreateUserRequest createUserRequest){
CreateUserCommand userCommand = CreateUserCommand.builder()
.email(createUserRequest.getEmail())
.name(createUserRequest.getName())
.nickname(createUserRequest.getNickname())
.password(createUserRequest.getPassword())
.build();
createUserUseCase.createUser(userCommand);
return SuccessApiResponse.of();
}
}
@Value
@EqualsAndHashCode(callSuper = false)
public class CreateUserRequest extends SelfValidating<CreateUserRequest> {
@Email
private final String email;
@NotBlank
private final String password;
@NotBlank
private final String nickname;
@NotBlank
private final String name;
public CreateUserRequest(String email, String password, String nickname, String name) {
this.email = email;
this.password = password;
this.nickname = nickname;
this.name = name;
this.validateSelf();
}
}
계층형 혹은 기능형 방식과 같은 기존 방식들은 구현하기 간단하다. 하지만 DB 처럼 외부 모듈에 의존하게 되면 경계가 모호하게 되기 때문에 공동작업을 하는데 큰 장애물이 될 것이다. 그래서 클린아키텍처로 관심사마다 독립성을 보장해줌으로써 확장성과 배포에 장점을 발휘할 것이라 기대된다.
일반적으로 우리가 주로 실습할 정도의 규모에는 적합하지 않다고 생각한다. 왜냐하면 작은 규모의 프로젝트에 적용하기에는 복잡한 트랜잭션을 요구하는 경우가 거의 없기 때문이다. 규모가 크고 핵심적인 비즈니스로직을 구현하고자 할때 적합하다고 생각한다. 훗날 대용량 트래픽처리와 복잡한 비즈니스 로직을 수행할 상황을 생각해 미리 공부해 둠으로써 개발의 선택지를 늘리고자 했다. 이전에 도메인 주도 설계에 관련된 책을 읽지 않았더라면 이 아키텍처에 대해서 거부감이 들었을 것이라 생각했다. 따라서 어떤 기술에 대해서 선택할때 그 방식을 사용해야 하는 이유에 대해 사전 지식을 먼저 습득을 해야만 자연스럽게 이해하기 쉬워지는 것 같다고 느꼈다.
정보 감사합니다.