on
Docker(CentOS 7) + Nginx + Spring Boot + Vue.js 배포하기 - ②
Docker(CentOS 7) + Nginx + Spring Boot + Vue.js 배포하기 - ②
① 편에서는 Docker를 사용하여 CentOS 7 이미지를 받고, 컨테이너를 실행하고, Nginx를 설치하는 작업을 해봤습니다. ② 편에서는 Vue.js에서 입력한 데이터를 저장하는 API, 데이터를 조회하는 API 이렇게 총 2개의 API를 가지는 간단한 Spring 어플리케이션을 만들어보도록 하겠습니다.
History
①: Docker + CentOS 7 + Nginx 패키지 설치
프로젝트 버전
개발 도구: 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
port를 9000번으로 띄울 거라는 것만 확인하고 넘어가시면 될 거 같습니다.
yml을 사용하고 싶으시면 기본적으로 생기는 application.properties 파일을 IntelliJ, Windows 10 기준 Shift + F6로 파일명을 application.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에 대한 설명은 아래 글을 참조해주시기 바랍니다.
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 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 { private T data; } @GetMapping public Result> members() { List results = memberRepository.findMemberResponseAll(); results.iterator().forEachRemaining(member -> { log.info("GET: Member List : {}", member); }); return new Result<>(results); } @PostMapping public Result 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 { @Query("select new com.wangtak.member.MemberResponse(m.email, m.name) from Member m") List 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를 호출하면 됩니다.
회원가입 API 호출
[Postman] POST API 호출
Member Table 확인
저장된 Member
CREATED_DATE , LAST_MODIFIED_DATE 를 보면 Entity가 생성된 시간을 자동으로 넣어주는 것을 알 수 있습니다.
, 를 보면 Entity가 생성된 시간을 자동으로 넣어주는 것을 알 수 있습니다. CREATED_BY , LAST_MODIFIED_BY 또한, UUID를 통해 생성된 랜덤 값이 들어가는 것을 알 수 있습니다.
, 또한, UUID를 통해 생성된 랜덤 값이 들어가는 것을 알 수 있습니다. EMAIL , NAME , PASSWORD 는 테스트를 위해 입력한 값이 잘 들어가 있는 것을 확인할 수 있습니다.
멤버 리스트 API 호출
[Postman] GET API 호출
② 편에서는 Spring을 사용하여 REST API를 만들어봤습니다. ③ 편은 ② 편에서 만든 API를 호출하고 화면에 보여주는 간단한 Vue.js 어플리케이션을 만들어 보도록 하겠습니다.
from http://wangtak.tistory.com/14 by ccl(A) rewrite - 2021-12-23 23:02:00