콘텐츠로 이동

확장하기

주요 컴포넌트 4개는 모두 교체할 수 있습니다. 해당 타입의 빈을 하나 등록하면 기본 구현이 물러납니다 (@ConditionalOnMissingBean).

SPI 기본 구현 바꾸는 이유
IssueFetcher JsoupIssueFetcher (공개 GitHub 이슈 페이지 조회) GitLab·Jira·사내 트래커, 비공개 레포(API 토큰)
IssueRepository JsonIssueRepository (JSON 파일) Redis·DB·인메모리 캐시
ChangelogRenderer HtmlTableChangelogRenderer (HTML 표) 카드형·타임라인형 출력
TitleNormalizer DefaultTitleNormalizer 팀별 이슈 제목 규칙(태그·이모지 제거 등)

모든 인터페이스는 kr.suhsaechan.suhapilog.spi 패키지에 있습니다.

IssueFetcher — 이슈를 어디서 가져올지

public interface IssueFetcher {
  // 실패하면 Optional.empty(). 예외를 던져 앱을 죽이지 않는다.
  Optional<IssueRef> fetch(int issueNumber);
}

예: 사내 트래커 연동

@Bean
public IssueFetcher trackerIssueFetcher(TrackerClient client) {
    return issueNumber -> client.findIssue(issueNumber)
        .map(i -> new IssueRef(issueNumber, i.title(), i.title(), i.url(), LocalDateTime.now()));
}

IssueRef(issueNumber, cleanTitle, rawTitle, pageUrl, fetchedAt) — cleanTitle이 표에 나오는 값이고, fetchedAt은 cache.ttl 만료 판단에 쓰입니다.

IssueRepository — 조회 결과를 어디에 저장할지

public interface IssueRepository {
  Optional<IssueRef> findByIssueNumber(int issueNumber);
  IssueRef save(IssueRef issue);
  Collection<IssueRef> findAll();
  void saveAll(Collection<IssueRef> issues);   // 쓰기를 1회로 묶어야 한다
  String getCurrentHash();                     // 마지막 동기화한 이슈 번호 집합의 해시
  void updateHash(String hash);
}

해시는 "이슈 번호 목록이 바뀌었는지"를 판단해 불필요한 재조회를 막는 데 쓰입니다.

ChangelogRenderer — 어떻게 보여줄지

public interface ChangelogRenderer {
  // entries 가 비면 빈 문자열을 반환해야 한다
  String render(List<ApiLogEntry> entries);
}

반환값은 Swagger의 API 설명 뒤에 이어 붙습니다. ApiLogEntry에는 작성자 기본값 처리와 이슈 제목 조회가 이미 끝난 값이 들어오므로 렌더러는 출력만 신경 쓰면 됩니다.

@Bean
public ChangelogRenderer bulletRenderer() {
    return entries -> entries.isEmpty() ? "" : entries.stream()
        .map(e -> "- " + e.date() + " " + e.author() + ": " + e.description())
        .collect(Collectors.joining("\n", "\n\n**변경 이력**\n\n", ""));
}

HTML 이스케이프

직접 렌더러를 만들면 이스케이프도 직접 책임집니다. 이슈 제목은 외부 사용자가 쓴 텍스트입니다.

TitleNormalizer — 이슈 제목을 어떻게 다듬을지

public interface TitleNormalizer {
  // rawTitle 은 null·빈 문자열일 수 있다. 절대 null 을 반환하지 않는다.
  String normalize(String rawTitle);
}

기본 구현은 🚀 [기능개선][태그] 같은 접두어를 걷어냅니다. 팀 규칙이 다르면 바꾸세요.

기여하기

새 기본 구현(예: GitLab 조회기)을 라이브러리에 넣고 싶다면 기여 가이드를 보고 이슈로 먼저 제안해 주세요.