메시지에 기능이 좀 더 필요하다고 생각해서, 나중에 다시하지 말고 그냥 지금 해놓기로.
메시지는 어떻게 생성되고, 어떻게 사용되는지 정리해보자. 예시를 들면 아래와 같다.
"안녕하세요 여러분! {author}님이 새로운 PR을 제출했어요: {title}. 리뷰는 {assignee}님께 할당되었습니다."
+ "여기서 확인할 수 있어요: [PR 링크]({link}) 꼼꼼하게 리뷰하고 피드백 부탁드려요. 감사합니다!";
여기서 {author}, {title} 등등은 유저가 등록할 템플릿에서 필수로 있어줘야 하는 것들이다. 실제 PR이 올라오면 위 메시지는
안녕하세요 여러분! 김철수님이 새로운 PR을 제출했어요: 새로운 기능 추가. 리뷰는 이영희님께 할당되었습니다. 여기서 확인할 수 있어요: [PR 링크](http://example.com) 꼼꼼하게 리뷰하고 피드백 부탁드려요. 감사합니다!
이런 식으로 변신하게 될 것이다. 변신된 메시지는 메신저(discord...) 로 전달된다.
즉
지금 일단 두 개의 동작이 나왔다. 한번 테스트를 작성해보자.
@Test
@DisplayName("필수 placeHolder 가 없을 시 예외가 발생한다.")
void createValidationTest() {
String userInput = "TEST";
assertThatThrownBy(() -> new MessageTemplate(userInput))
.isInstanceOf(ReviewMessageException.MissingPlaceholderException.class)
.hasMessage("필수 메시지 입력값이 누락되었습니다. 누락된 값 : AUTHOR_NAME");
}
@Test
@DisplayName("ReviewMessage 객체 생성에 성공한다.")
void createTest() {
String template = "안녕하세요 여러분! {author}님이 새로운 PR을 제출했어요: {title}. 리뷰는 {assignee}님께 할당되었습니다."
+ "여기서 확인할 수 있어요: [PR 링크]({link}) 꼼꼼하게 리뷰하고 피드백 부탁드려요. 감사합니다!";
MessageTemplate messageTemplate = new MessageTemplate(template);
assertThat(messageTemplate).isNotNull();
}
...
@Entity
@NoArgsConstructor
@Getter
public class MessageTemplate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String message;
public MessageTemplate(String message) {
validate(message);
this.message = message;
}
private void validate(String message) {
Arrays.stream(TemplatePlaceholder.values())
.filter(placeholder -> !message.contains(placeholder.getPlaceholder()))
.findFirst()
.ifPresent(placeholder -> {
throw new ReviewMessageException.MissingPlaceholderException(placeholder.name());
});
}
}
코드는 위와 같은 형태로 작성 해 보았다. 우리는 템플릿에 들어갈 {prTItle} 같은 녀석들을 TemplatePlaceholder 라는 enum 으로 관리하므로, 객체의 생성자에서 이를 통해 validate 할 수 있다.
그렇다면 이 템플릿에서, 필요한 부분을 실제 PR 정보로 바꾸기 위해서는 어떻게 해야할까?
나는 2번을 선택하고 싶다. 왜냐면 DefaultMessage 는 반드시 깃헙 PR의 정보가 필요하다. 그리고 깃헙 PR 정보는 외부 API를 통해서 불러와야 한다.
MessageTemplate 라는 깜찍이 도메인이 커져봐야 얼마나 커지겠냐고 생각하긴 하지만, 난 JPA 엔티티를 도메인과 비슷하게 사용하기를 원하고 있는 상태이므로 외부 API를 통해서 정보를 가져와야만 영속화 할 수 있는 형태로 구현하고 싶지는 않다.
더불어 MessageTemplate 에서 VO로 관리하게 된다면, DefaultMessage 의 정보들은 DB에 없으므로 반드시 외부에서 값을 주입받아야 한다. 의미없다고 생각했다.
따라서 DefaultMessage 는 Jpa Entity 가 아닌 나만의 깜찍한 도메인으로 남기고, 이 도메인은 외부 API 에 의해서만 생성되는 것으로 남겨두자. 이렇게 함으로써 도메인은 도메인만, 인프라쪽은 인프라만 테스트하기도 용이 할 것이다.
다시 돌아와서, 오케이! 그러면 메시지의 조합을 해서 마지막 결과를 내놓는 쪽은 어디어야 하는가? 나는 DefaultMessage, MessageTemplate 이 아닌.Service여야고 본다. (조회관련 작업은 서비스에게 주고싶다)
간단히 작성하고 테스트해보자
@Service
@RequiredArgsConstructor
@Transactional
public class MessageService {
private final MessageTemplateRepository messageTemplateRepository;
// GithubPrResponse 를 주는 쪽, 그리고 assignee 를 주는 쪽을 호출하기로 한다. 혹은 호출당하기로 한다.
public String getDefaultMessage(String githubRepositoryId, GithubPRResponse githubPRResponse, String assignee){
DefaultMessage defaultMessage = githubPRResponse.toDefaultMessage(assignee);
MessageTemplate messageTemplate = messageTemplateRepository.findByGithubRepositoryId(githubRepositoryId).orElseThrow(
() -> new MessageTemplateException.MessageTemplateNotFoundException(githubRepositoryId)
);
String template = messageTemplate.getTemplate();
return defaultMessage.mergeMessage(template);
}
}
...
@SpringBootTest
class MessageServiceTest {
@MockBean
private MessageTemplateRepository messageTemplateRepository;
@Autowired
private MessageService messageService;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testGetDefaultMessage() {
// arrange
String githubRepositoryId = "githubRepositoryId";
String assignee = "assignee";
String template = "{author} {title} {assignee} {link}";
GithubPRResponse githubPRResponse = new GithubPRResponse("prTitle", "prLink", "prAuthor");
MessageTemplate messageTemplate = new MessageTemplate(template, githubRepositoryId);
when(messageTemplateRepository.findByGithubRepositoryId(githubRepositoryId)).thenReturn(Optional.of(messageTemplate));
// act
String defaultMessage = messageService.getDefaultMessage(githubRepositoryId, githubPRResponse, assignee);
// assert
String expected = "prAuthor prTitle assignee prLink";
assertThat(defaultMessage).isEqualTo(expected);
}
}
간단한 흐름은 위와 같다.
코드 전문은
요기에서