Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] swagger config setup #8

Merged
merged 15 commits into from
Feb 6, 2025

Conversation

Hoya324
Copy link
Member

@Hoya324 Hoya324 commented Feb 2, 2025

관련 이슈가 있다면 적어주세요.

issue #2

📌 개발 이유

  • 프로젝트의 API 문서를 자동화하기 위해 Swagger(OpenAPI 3.0) 설정을 추가.
  • 환경별 서버 URL 설정 및 헬스 체크 API를 문서화.

💻 수정 사항

1. Swagger 설정 추가 (SwaggerConfig.java)

  • OpenAPI 문서화 설정 추가.
  • swagger.version 값을 application.yml에서 주입받도록 설정.
  • 환경별 서버 URL을 동적으로 설정하기 위해 SpringEnvironmentUtil을 활용.
  • API 정보를 Info 객체에 정의 (제목, 설명, 버전, GitHub 링크 포함).

2. URL 및 환경 상수 정의 (UrlConstants.java, EnvironmentConstants.java)

  • UrlConstants.java
    • 로컬 서버(8080), 로컬 웹(3000) URL 상수 추가.
  • EnvironmentConstants.java
    • prod, dev, local 환경 상수 추가.

3. 현재 활성화된 Spring Profile을 반환하는 유틸리티 (SpringEnvironmentUtil.java)

  • 현재 환경을 가져와 prod, dev, local 중 하나로 반환.
  • isProdProfile(), isDevProfile(), isProdAndDevProfile() 등의 메서드 추가.

4. Swagger 관련 설정 추가 (appication-dev.yml, application-prod.yml)

  • Swagger UI 경로를 /swagger-ui로 설정.
  • 기본 요청/응답 미디어 타입을 application/json;charset=UTF-8로 지정.

5. 헬스 체크 API 문서화 (HealthApi.java, HealthController.java)

  • HealthApi.java: API 설명을 추가하는 Swagger 어노테이션 적용.
  • HealthController.java: /api/health 엔드포인트에서 "ok" 응답을 반환.

🧪 테스트 방법

  1. 서버 실행 후 Swagger UI 접근:
    • http://localhost:8080/swagger-ui/index.html
  2. /api/health 엔드포인트 호출 후 "ok" 응답이 오는지 확인.
Screenshot 2025-02-02 at 17 39 35

⛳️ 고민한 점 || 궁금한 점

  • 현재는 prod, dev 서버 URL이 연결되지 않아, 향후 배포 서버 주소 확정 후 반영
  • 현재 서비스명이 정해지지 않아 Swagger Info가 임시로 정해져있음
  • 스웨거의 기본 설정만 추가한 상태라 추후 보안(swagger 로그인?)기능을 추가해야하지 않을까? 라는 생각이 들었습니다.

🎯 리뷰 포인트

  • 전역 상수 EnvironmentConstants, UrlConstantsSpringEnvironmentUtil 유틸리티 를 적용했는데, 그 필요성에 대해 논의되지 않은 상태라 이에 대해 의견 부탁드립니다!

브랜치 설정을 잘못해서 다시 올립니다..!

@Hoya324 Hoya324 added the ✨ Feature 기능 추가 구현 label Feb 2, 2025
@Hoya324 Hoya324 self-assigned this Feb 2, 2025
Copy link

coderabbitai bot commented Feb 2, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Hoya324 Hoya324 added the ❓ Question 해당 작업에 대한 궁금증, 의문, 논의사항 label Feb 2, 2025
Copy link
Collaborator

@helenason helenason left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swagger 설정 추가해주셔 감사해요. PR description이 자세해서 리뷰하기 더욱 수월했던 것 같아요!!

추가로 몇가지 제안과 질문 남깁니다!! 감사합니다 :)

Comment on lines 14 to 28
public enum EnvironmentConstants {
PROD(PROD_ENV),
DEV(DEV_ENV),
LOCAL(LOCAL_ENV);

private final String value;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Constants {
public static final String PROD_ENV = "prod";
public static final String DEV_ENV = "dev";
public static final String LOCAL_ENV = "local";
public static final List<String> PROD_AND_DEV_ENV = List.of(PROD_ENV, DEV_ENV);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[컨벤션]

이런 얘기는 저희끼리 안 한 것 같긴 한데 다른 클래스들과 동일하게 첫줄 공백 주는 게 좋을 것 같습니다!
Constants와 UrlConstants 클래스도 확인 부탁드려요 !!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정했습니다~~

5aeeaf4

Comment on lines 47 to 71
private List<Server> swaggerServers() {
Server server = new Server().url(getServerUrl()).description(API_DESCRIPTION);
return List.of(server);
}

private String getServerUrl() {
return switch (springEnvironmentUtil.getCurrentProfile()) {
// TODO: prod, dev 연결
// case "prod" -> UrlConstants.PROD_SERVER_URL.getValue();
// case "dev" -> UrlConstants.DEV_SERVER_URL.getValue();
default -> UrlConstants.LOCAL_SERVER_URL.getValue();
};
}

private Info swaggerInfo() {
License license = new License();
license.setUrl(GITHUB_URL);
license.setName(SERVER_NAME);

return new Info()
.version("v" + version)
.title(API_TITLE)
.description(API_DESCRIPTION)
.license(license);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[컨벤션] 마찬가지로 여기 private 메서드도 사용하는 쪽 openAPI() 아래에 두면 가독성이 더욱 좋아질 것 같아요~~~!

+) 전체적인 파일 대상으로 EOL 확인 부탁드려용

Copy link
Member Author

@Hoya324 Hoya324 Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨벤션 자꾸 안 지켜서 죄송합니다.!!!

746b246

Comment on lines 21 to 27
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Constants {
public static final String PROD_ENV = "prod";
public static final String DEV_ENV = "dev";
public static final String LOCAL_ENV = "local";
public static final List<String> PROD_AND_DEV_ENV = List.of(PROD_ENV, DEV_ENV);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants 클래스를 static으로 선언하신 이유가 궁금해요! static이 붙은 클래스는 내부 클래스로 선언해야하니까 EnvironmentConstants enum 클래스도 생성된 것으로 보이는데, EnvironmentConstants 클래스의 추가적인 역할이 없는 것 같아서요! 만약 static 을 떼면 위 enum 클래스도 불필요해져 SpringEnvironmentUtil와 같은 외부에서 상수들을 사용할 때 depth가 적어져 좋아질 것 같다는 의견입니다!

아니면 제가 생각하지 못한 EnvironmentConstants 클래스의 다른 목적이 있는 것인지 궁금해요!!!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내부 클래스에 static을 붙이는 이유는
static 없는 내부 클래스: 외부 클래스 참조(숨은 this$0 변수) → 메모리 누수 가능
static 있는 내부 클래스: 외부 클래스 참조 없음 → 메모리 관리 안전
으로 얘기할 수 있을거 같아요! 관련 레퍼런스

다만 채영님이 질문하신 내용이 EnvironmentConstants의 필요성에 대한 거라면 Enum으로 관리함으로써 환경 변수를 타입 세이프하게 관리할 수 있다는 장점에 더해 값을 하드 코딩하는 것이 아닌 내부 클래스를 각 환경에 맞는 변수를 설정하고자 함이었습니다!! 제가 잘못이해한 부분이 있다면 말씀해주세요!!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다만 채영님이 질문하신 내용이 EnvironmentConstants의 필요성에 대한 거라면 Enum으로 관리함으로써 환경 변수를 타입 세이프하게 관리할 수 있다는 장점에 더해 값을 하드 코딩하는 것이 아닌 내부 클래스를 각 환경에 맞는 변수를 설정하고자 함이었습니다!! 제가 잘못이해한 부분이 있다면 말씀해주세요!!

맞습니다. EnvironmentConstants의 필요성에 관해 드렸던 질문이었어요!

저의 경우는 EnvironmentConstants 없이 Constants만(물론 다른 이름으로) 상위 클래스에 존재하여도 외부에서 Constanst.PROD_ENV와 같이 사용할 수 있기 때문에 타입안전과 하드코딩방지 측면 모두에서 만족시킬 수 있는 방법이라 생각하긴 하는데요! 경호님이 말씀하신 장점이 이 장점이 맞을까요?!?! 제가 잘못 이해했나, 혹은 이전 리뷰에서 잘 전달드리지 못했나 싶어 한번 더 여쭤봅니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

원래는 enum만으로 관리할 때 LOCAL.name() 처럼 상수에서 메서드를 굳이 호출하는게 좋지 않다고 생각해서 enum으로 관리하되, 외부에서는 상수를 호출하는 것처럼 관리하려했습니다!
채영님 말씀을 듣고 생각해보니 상수를 굳이 enum으로 관리할 필요는 없겠더라구요!! 그래서 수정했습니다!! 좋은 의견 감사해요!!!

3255681

아래는 enum만으로 관리할 때 LOCAL.name() 처럼 상수에서 메서드 호출 예시입니다! name 을 호출하는것도 문제지만 개인적으로 prod와 dev를 함께 확인하기 위한 메서드도 지금 굳이.?라는 생각이 들어서 위의 수정처럼 class에서 상수를 관리하고자합니다! 이 부분도 의견 있으시면 반영할게요!! 완전 필요한건 아니라고 생각해서 의견 많이많이 내주시면 좋습니다!

@Getter
@AllArgsConstructor
public enum EnvironmentConstants {

	PROD("prod"),
	DEV("dev"),
	LOCAL("local"),
	;

	private final String value;
	
	public static List<String> getProdAndDevEnv() {
		return List.of(PROD.name(), DEV.name());
	}
}
@Component
@RequiredArgsConstructor
public class ProfileResolver {

	private final Environment environment;

	public String getCurrentProfile() {
		return getActiveProfiles()
			.filter(profile -> profile.equals(PROD.name()) || profile.equals(DEV.name()))
			.findFirst()
			.orElse(LOCAL.name());
	}

	public boolean isProdProfile() {
		return getActiveProfiles().anyMatch(PROD.name()::equals);
	}

	public boolean isDevProfile() {
		return getActiveProfiles().anyMatch(DEV.name()::equals);
	}

	public boolean isProdAndDevProfile() {
		return getActiveProfiles().anyMatch(getProdAndDevEnv()::contains);
	}

	private Stream<String> getActiveProfiles() {
		return Arrays.stream(environment.getActiveProfiles());
	}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 방식 좋은 것 같습니다!! 의견 내기 조금 조심스러웠는데 잘 듣고 반영해주셔서 감사해요 🥹

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 아~~~~무 걱정말고 많이많이 피드백해주세요!! 감사합니다ㅎㅎㅎ

Comment on lines 26 to 36
public boolean isProdProfile() {
return getActiveProfiles().anyMatch(PROD_ENV::equals);
}

public boolean isDevProfile() {
return getActiveProfiles().anyMatch(DEV_ENV::equals);
}

public boolean isProdAndDevProfile() {
return getActiveProfiles().anyMatch(PROD_AND_DEV_ENV::contains);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 메서드는 사용되지 않는 메서드로 보이는데, 어떤 목적으로 구현하셨는지 궁금합니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 저도 고민했던 부분인데요!! 아직은 사용하지 않을 메서드긴하지만,

추후에 인프라 세팅이 완료된 근시점에 필요한 메서드라고 생각해서 넣어두었습니다!! 아래는 그 예시입니다!

    @Bean
    @Order(1)
    @ConditionalOnProfile({PROD, DEV, LOCAL})
    public SecurityFilterChain swaggerFilterChain(HttpSecurity http) throws Exception {
        defaultFilterChain(http);

        http.securityMatcher(getSwaggerUrls()).httpBasic(withDefaults());

        http.authorizeHttpRequests(
                (springEnvironmentUtil.isDevProfile() || springEnvironmentUtil.isProdProfile())
                        ? authorize -> authorize.anyRequest().authenticated()
                        : authorize -> authorize.anyRequest().permitAll());

        return http.build();

이외에도 Cookie 관련된 세팅에 환경에 따라 다르게 설정(예: Samsite)하는 경우도 있다고 알고 있어서 이부분 추가 의견 부탁드려요!!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추후 사용될 메서드라면 삭제하지 않아도 좋을 것 같아요! 설명 감사합니다!!


@Component
@RequiredArgsConstructor
public class SpringEnvironmentUtil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 클래스가 profile을 관리하는 역할을 담당했다는 걸 한번에 인지하지 못했었는데요..! 클래스명이 조금은 추상적이라고 생각했습니다! 다른 이름으로 변경하는 것은 어떨지 조심스럽게 제안드려봅니다! (ProfileResolver, ActiveProfileResolver, ActiveProfileProvider 등...?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProfileResolver가 더 적합해보여서 변경했습니다! 좋은 의견 감사해요!!

20a3265

Comment on lines +1 to +4
spring:
config:
activate:
on-profile: dev
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 dev, prod와 같은 profile의 이름이 yml 파일에 정의되어 있다면 @Value를 통해서 active profile 이름을 가져올 수 있을 것이라고 생각되는데요.

@Value("${spring.config.activate.on-profile}")
private String activeProfile;

위와 같은 방식이 아닌, SpringEnvironmentUtil 클래스 내 Environment 클래스를 통해서 activeProfile을 가져오도록 하신 이유가 궁금합니다!!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 사실 스타일(?) 차이라고 생각하는데요! 개인적으로는 어느정도 공통으로 쓰이는 것은 util로 뒀던 편이라 이렇게 구현했습니다!
@value 방식이 더 적합해보인다면 찬기님 의견도 들어보고 바꿔도 좋을거 같아요!!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문자열 값이 두 곳에서 함께 관리되어 휴먼에러 발생 측면에서 걱정이 돼 드렸던 리뷰였는데요! 스타일 차이이기도 하고 사실 dev, prod와 같은 값들은 휴먼에러 발생 가능성이 낮으니 유지해도 좋을 것 같아요!

Copy link
Member

@Lim-Changi Lim-Changi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 정말 고민해서 Swagger 세팅을 해주신게 눈에 띄네요...!! 정말 고생하셨습니다!!!

다만 구현 맥락은 어느정도 이해하였는데, 전체적으로 파편화가 많이 되어있다보니 현재 PR 에선 추가 설명이 더 필요할 것 같습니다..!

추가로 저희 JWT 를 활용할 예정이라면, Swagger 에 Authorization Bearer 헤더 넣을 수 있는 설정도 포함해주시면 좋을 것 같아요! (아래는 예시)

Components components = new Components().addSecuritySchemes(jwtSchemeName,
                        new SecurityScheme()
                        .type(SecurityScheme.Type.APIKEY)
                        .in(SecurityScheme.In.HEADER)
                        .name("Authorization")

                );

@Hoya324
Copy link
Member Author

Hoya324 commented Feb 3, 2025

추가로 저희 JWT 를 활용할 예정이라면, Swagger 에 Authorization Bearer 헤더 넣을 수 있는 설정도 포함해주시면 좋을 것 같아요! (아래는 예시)

오 이 부분도 고민했던 부분인데 집어주셔서 감사합니다! 추가했어용

35f06fb

Copy link
Collaborator

@helenason helenason left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다!!! PR이 자세해서 코드 읽기 수월했던 것 같아요 :)

@Hoya324 Hoya324 merged commit 4fd0dfb into feat/2-initial-setup Feb 6, 2025
1 check passed
@Hoya324 Hoya324 deleted the feat/2-swagger-config-setup branch February 6, 2025 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ Feature 기능 추가 구현 ❓ Question 해당 작업에 대한 궁금증, 의문, 논의사항
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants