Nginx

Docker(CentOS 7) + Nginx + Spring Boot + Vue.js 배포하기 - ②

WangTak 2021. 12. 23. 22:01
반응형

① 편에서는 Docker를 사용하여 CentOS 7 이미지를 받고, 컨테이너를 실행하고, Nginx를 설치하는 작업을 해봤습니다. ② 편에서는 Vue.js에서 입력한 데이터를 저장하는 API, 데이터를 조회하는 API 이렇게 총 2개의 API를 가지는 간단한 Spring 어플리케이션을 만들어보도록 하겠습니다.

 

History

①: Docker + CentOS 7 + Nginx 패키지 설치

[Nginx] - Docker(CentOS 7) + Nginx + Spring Boot + Vue.js 배포하기 - ①

 

프로젝트 버전

  • 개발 도구: IntelliJ Ultimate
  • Spring Boot: 2.5.8
  • Java 11
  • h2 Database
  • Gradle
  • Packaging: Jar
  • Dependencies: Spring Web, Spring Data JPA, Lombok, H2 Database

 

간단한 어플리케이션이기 때문에 따로 패키지를 나누지 않고 기본 패키지에 클래스와 인터페이스를 만들도록 하겠습니다. build.gradle 파일의 경우는 프로젝트 생성 이후 따로 추가할 부분은 없습니다.

 

프로젝트 설정 파일은 application.yml을 사용하였으며 다음과 같습니다.

 

application.yml

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/h2/tistory
    username: sa
    password: sa
    driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true

server:
  port: 9000

 

application-dev.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/wangtak?serverTimezone=UTC&characterEncoding=UTF-8
    username: sa
    password: cleancode!!
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        format_sql: true

server:
  port: 9000

 

  • application.yml은 로컬 환경의 설정 파일입니다.
  • application-dev.yml은 개발 환경의 설정 파일입니다. ① 편에서 생성한 MySQL의 계정과 비밀번호를 적어줍니다.
  • 로컬은 h2 DB를, 개발(Docker 컨테이너)은 MySQL DB를 사용할 예정이기 때문에 파일을 분리
  • 포트는 공통적으로 9000번으로 실행
yml을 사용하고 싶으시면 기본적으로 생기는 application.properties 파일을 IntelliJ, Windows 10 기준 Shift + F6로 파일명을 application.yml로 바꾸시면 됩니다.
application-dev.yml은 추가해주시면 됩니다.

 

프로젝트 구조

패키지 구조

 

각 클래스마다 라인 수가 많이 길지 않기 때문에 다 포스팅하도록 하겠습니다. 위 사진 기준으로 위에서부터 아래로 정리하겠습니다.

 

BaseEntity

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public abstract class BaseEntity extends BaseTimeEntity{

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
}

 

BaseTimeEntity

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public abstract class BaseTimeEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

 

위 2개의 클래스는 Spring Data JPA에서 제공하는 Auditing을 위한 클래스입니다. Auditing에 대한 설명은 아래 글을 참조해주시기 바랍니다.

[Spring Data JPA] - Spring Data JPA - Auditing

 

Member

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;
    private String password;
    private String name;

    @Builder
    private Member(String email, String password, String name) {
        this.email = email;
        this.password = password;
        this.name = name;
    }
}

 

MemberApplicaiton

@EnableJpaAuditing // Auditing을 위한 어노테이션
@SpringBootApplication
public class MemberApplication {

	public static void main(String[] args) {
		SpringApplication.run(MemberApplication.class, args);
	}

	// @CreatedBy, @LastModifiedBy에 반응할 빈 등록
	// Spring Security를 사용한다면 SecurityContextHolder에서 사용자 정보를 넣어줍니다
	@Bean
	public AuditorAware<String> auditorProvider() {
		return () -> Optional.of(UUID.randomUUID().toString().subString(0, 8));
	}

}

 

MemberController

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/members")
public class MemberController {

    private final MemberRepository memberRepository;

    @Data
    @AllArgsConstructor
    static class Result<T> {
        private T data;
    }

    @GetMapping
    public Result<List<MemberResponse>> members() {
        List<MemberResponse> results = memberRepository.findMemberResponseAll();

        results.iterator().forEachRemaining(member -> {
            log.info("GET: Member List : {}", member);
        });

        return new Result<>(results);
    }

    @PostMapping
    public Result<SignupResponse> signup(@RequestBody SignupRequest signupRequest) {
        log.info("POST: SignupRequest : {}", signupRequest);

        // SignupRequest to Entity
        Member member = Member.builder()
                .email(signupRequest.getEmail())
                .password(signupRequest.getPassword())
                .name(signupRequest.getName())
                .build();

        // Entity Save
        Member savedMember = memberRepository.save(member);

        // new SignupResponse
        SignupResponse signupResponse = 
        	new SignupResponse(savedMember.getEmail(), savedMember.getName(), "회원 가입 성공");

        return new Result<>(signupResponse);
    }
}

API 응답을 보낼 때 Member List처럼 배열을 그대로 보내게 되면 API 스펙이 유연하지 못하기 때문에 특정 객체로 감싸서 응답을 보내는 것이 좋습니다. 그래서 Result 클래스에 Generic을 사용하였습니다.

 

MemberRepository

public interface MemberRepository extends JpaRepository<Member, Long> {

    @Query("select new com.wangtak.member.MemberResponse(m.email, m.name) from Member m")
    List<MemberResponse> findMemberResponseAll();
}

Member 타입으로 조회해서 Response로 변환해도 되지만 바로 MemberResponse 타입으로 조회하게끔 하였습니다.

Querydsl을 사용하면 너저분한 package 경로 없이 사용할 수 있습니다.

 

API Spec(Request, Resposne)에 Entity를 직접 노출하는 것은 바람직하지 않은 설계이기 때문에 아래와 같은 API 전용 클래스를 사용하였습니다.

 

 

MeberResponse

Member의 정보를 보여줄 때 password는 보이면 안 되기 때문에 password를 제외한 Response 클래스

@Data
public class MemberResponse {

    private String email;
    private String name;

    public MemberResponse(String email, String name) {
        this.email = email;
        this.name = name;
    }
}

 

 

SignupRequest

회원 가입을 위한 Request 클래스 

@Data
public class SignupRequest {

    private String email;
    private String password;
    private String name;
}

 

 

SignupResponse

회원 가입이 완료되고 가입 완료됐음을 알려줄 Response 클래스

@Data
public class SignupResponse {

    private String email;
    private String name;
    private String msg;

    public SignupResponse(String email, String name, String msg) {
        this.email = email;
        this.name = name;
        this.msg = msg;
    }
}

 

API 테스트하기

 

어플리케이션을 실행하여 테스트를 해보도록 하겠습니다. 테스트는 Postman을 사용하였습니다. application.yml 파일에 설정했다시피 9000번 포트로 API를 호출하면 됩니다. [여기서는 application.yml의 설정 파일로 실행되기 때문에 로컬(호스트 PC)에 있는 h2 DB에 커넥션이 이루어집니다.]

 

회원가입 API 호출

[Postman] POST API 호출

 

Member Table 확인

저장된 Member

 

  • CREATED_DATE, LAST_MODIFIED_DATE를 보면 Entity가 생성된 시간을 자동으로 넣어주는 것을 알 수 있습니다.
  • CREATED_BY, LAST_MODIFIED_BY 또한, UUID를 통해 생성된 랜덤 값이 들어가는 것을 알 수 있습니다.
  • EMAIL, NAME, PASSWORD는 테스트를 위해 입력한 값이 잘 들어가 있는 것을 확인할 수 있습니다.

 

멤버 리스트 API 호출

[Postman] GET API 호출

 

② 편에서는 Spring을 사용하여 REST API를 만들어봤습니다. ③ 편은 ② 편에서 만든 API를 호출하고 화면에 보여주는 간단한 Vue.js 어플리케이션을 만들어 보도록 하겠습니다.

 

 

반응형