JAVA 17, Spring Boot 3.0.2, Gradle, MySQL, Redis로 만드는
'오늘의코딩' 메일링 서비스

 

 

메인 페이지를 하나의 Step에 묶에서 블로그 글을 쓰려고 했는데, 구지 프론트만 다 만들어 놓고 하는 것보다 아래 하단에 보이는 서비스 나열을 백엔드로 가지고 와서 프론트를 한 번에 처리하면 편할 것 같아서, 백엔드로 데이터를 추가하고 Redis 설정하는 부분까지 설명하려고 Step을 나눠서 작성했다. 

 

이번 페이지는 Step7을 진행하기 앞서 생각한 고민들을 설계에 어떻게 반영할지가 담긴 고찰 로그라고 할 수 있다. 부디 이 글을 보고 있는 분들은 프로젝트 설계에 한 번에(?) 성공하시길 바라면서 작성했다.

 

 

Rest API 명세를 위한 Swagger 적용


백엔드 개발에서 빼놓고는 설명할 수 없는 부분이 바로 Rest API이다. 최종 API 명세 출력이 용이하도록 swagger 처리를 해두었다. 개발하면서 보고 API를 구현/정리할 예정이다.

 

Swagger ui 사용하는 방법

swagger를 이용한 Rest API 구현처리는 의존성 추가와 SwaggerConfig 설정, @ApiOperation 으로 명세에 주석달기로 진행할 수 있다.

 

먼저 spring 프로젝트의 build.gradle에 의존성을 추가해준다. 나는 명세 리스트만 나오면 되기 때문에 2.x.x 대 버전을 사용했는데, 최신 버전은 4.18.0 이라고 한다.

 

implementation group: 'io.springfox', name: 'springfox-swagger2', version: '2.9.2'
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2'

 

그리고 config 파일을 하나 만들어야 swagger ui를 사용할 준비가 된다. 

여기서 조심할 점은 클래스 앞에 @Configuration 만 처리를 하면 에러가 날 수 있으니 조심하자. 여기서 @EnableWebMvc를 함께 사용해줘야 SpingMVC에서 사용이 가능하다. 아니면, @EnableSwagger2만 적용하는 방법으로 해결한 사람도 있다.

java - Spring Boot Swagger 2 Configuration Error creating bean with name 'documentationPluginsBootstrapper' - Stack Overflow

 

package com.toco.trialService.config;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableWebMvc
public class SwaggerConfig {
    private static final String SERVICE_NAME = "Today Coding";
    private static final String API_VERSION = "v1.0.0";
    private static final String API_DESCRIPTION = "Today Coding API";
    private static final String API_URL = "http://localhost:8080/";

    public Docket swagger(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title(SERVICE_NAME)
                .version(API_VERSION)
                .description(API_DESCRIPTION)
                .termsOfServiceUrl(API_URL)
                .licenseUrl("/")
                .build();
    }
}

 

@ApiOperation 으로 명세에 주석달기 위해서는 각각의 컨트롤러 메서드 위에 @[xxxxx]Mapping 처리 부분에 어노테이션을 각각 달아주면 된다. 지금 모든 컨트롤러에 해 놓으면 코드가 깔끔해보이지 않아서 이 부분은 명세를 출력하기 직전에 적용해보기로 했다.

 

@ApiOperation(value="로그인", notes="로그인 기능")

 

 

ERD 추가 수정과 수정과 수정의 결과..!


ERD를 또 수정해봤다. 수정하면서 고민한 내용들을 간략하게 정리해봤다.

생각하면 할수록 하고 싶은 기능들이 늘어나는데 욕심을 멈추고 이 정도 선의 서비스까지만 제공하자는 생각이 들어 이 페이지에 박제하려고 한다.

 

1. 서비스별 이미지 경로가 필요해

생각해보니까 각각의 배너 이미지를 만드는 것도 일이 될 것 같다. 적어도 메인 페이지에 이미지 6개 + 각각의 상세 페이지 이미지 6개니까 서비스별 이미지 만드는 걸 좀 더 생각해봐야겠다. 일단은 샘플 이미지를 사용해서 만들고 컴퓨터에 있는 이지를 불러와서 적용하는 과정을 거쳐야 할 것 같다.

이미지 소스를 저장해야 되므로 기존 서비스 테이블에서 source 연결을 위한 image_path 컬럼을 추가했다.

 

2. 공통 엔티티의 추가

그리고 각각의 테이블에서 등록일, 수정일이 필요한 경우가 있을 것 같아 공통적으로 사용되는 BaseEntity 설정을 위한 컬럼을 추가했다.

 

3. enum을 잘 사용해보자 vs. 유지보수 편리한 서비스를 만들자

이번 프로젝트 서비스는 특성상 서비스별 카테고리를 세분화해서 관리할 필요가 있었다. 서비스 카테고리를 db에 넣어놓고 관리자가 직접 유지보수할 수 있도록 할지 그냥 내가 관리자다 생각하고 아래처럼 enum을 대분류, 소분류로 나누어서 리스트로 관리할지에 대한 고민이었다. 

결론은 나중에 유지보수를 위한 db 처리로 진행하기로 했다. 보여주기 위한 프로젝트도 좋지만 진짜 나중에 사람들이 사용할 수도 있으니, 모든 이해관계자에게 편리한 기능을 만들어야겠다고 생각해서 category 테이블을 생성했다.

 

public enum Category {
    CS("컴퓨터 상식", Arrays.asList(Service.CS, Service.TREND)),
    Language("언어", Arrays.asList(Service.C1, Service.C2, Service.C3, Service.JAVA, Service.JS, Service.HTML_CSS, Service.PYTHON, Service.GO)),
    Framework("프레임워크", Arrays.asList(Service.SPRING, Service.DJANGO));
    
    private final String name;
    private final List<Service> services;

    Category(String name, List<Service> services) {
        this.name = name;
        this.services = services;
    }

    public String getName() {
        return name;
    }
    
}
public enum Service {
    CS("CS"),TREND("TREND"),
    JAVA("JAVA"), PYTHON("PYTHON"), C1("C"), C2("C++"), C3("C#"), JS("JS"), HTML_CSS("HTML/CSS"), GO("GO"),
    SPRING("SPRING"), DJANGO("DJANGO"),
    Architecture("Architecture"), API("API"),
    MSSQL("MSSQL"), MYSQL("MYSQL"), MARIADB("MARIADB"), ORACLE("ORACLE"),
    KUBERNETES("KUBERNETES"), DOCKER("DOCKER");

    private final String Name;

    Service(String name){
        this.Name = name;
    }

    public String getName() {
        return Name;
    }
}

 

4. 연관관계 매핑의 변경

연관관계가 딱히 필요하지 않다고 생각한 부분들의 mapping을 지웠다. 로그와 같은 경우 등록자(?) 즉, 로그인한 사람의 정보를 다 끌어올 이유가 별로 없을 것 같아서 매핑하지 않았다. 그리고 이전에 있던 순환 구조의 매핑을 변경해서 결과적으로 단방향 설정이 가능하도록 변경했다. (User, Service, Progress 부분)

 


그렇게 탄생한 고찰의 산물..!

 

댓글