Rental Application (React & Spring boot Microservice) - 12 : post-service(1)

#1 post-service 개요

post-service는 유저가 게시물을 올리기 위한 서비스입니다. 물건을 대여하거나 대여를 요청하기 위한 공간이죠. 대략적인 시나리오를 한번 보겠습니다.

1) 물건대여 - 빌려줄게요
1-1) 유저-a는 z라는 물건을 대여해주기 위해 빌려줄게요라는 게시글 타입으로 게시글을 작성합니다.
1-2) 유저-b는 z를 대여하기 위해 대여할게요 버튼을 누르고 대여를 진행합니다.
1-3) 대여에 관한 정보는 유저-a가 유저-b에게 대여해줄지에 대한 승낙을 진행하기 위한 댓글 혹은 message-service로 대화를 나누고 rental-service를 거치고 나서 대여를 진행합니다.

2) 물건대여 - 빌려주세요
2-1) 유저-a는 d라는 물건을 대여하고 싶지만 빌려줄게요 게시판에는 d라는 물건이 보이지 않습니다.
2-2) 유저-a는 d라는 물건을 빌리기 위해 빌려주세요라는 게시글 타입으로 게시글을 작성합니다.
2-3) 유저-b는 해당 게시글을 보고 댓글 혹은 메시지 기능을 이용해 유저-a와 대여에 관한 정보를 나눕니다.
2-4) 유저-b는 빌려줄게요라는 게시글 타입으로 게시글을 작성합니다.
2-5) 1-2) ~ 1-3)을 진행합니다.

  • 애플리케이션에 대해 지속적으로 고민한 결과 메시지에 관한 message-service 부분이 필요할 것 같아 추가했습니다. 추후에 message-service를 구현하면서 intro부분을 수정하도록 하겠습니다.
  • post-service 테이블

post-service는 총 3개의 테이블로 이루어질 예정이고, post_id를 외래키로 하여 image테이블, comments테이블을 연결하도록 하겠습니다.

#2 프로젝트 생성

다음과 같이 프로젝트를 생성하도록 하겠습니다.

우선 설정 파일인 application.yml파일과 bootstrap.yml파일을 생성하고 pom.xml파일에서 필요한 디펜던시를 추가하도록 하겠습니다.

  • application.yml
server:
  port: ${port:7100}

spring:
  application:
    name: post-service

  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

  jpa:
    generate-ddl: true
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        use_sql_comments: true

eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka

management:
  endpoints:
    web:
      exposure:
        include: refresh, health, beans, busrefresh
  • bootstrap.yml
spring:
  cloud:
    config:
      uri: http://127.0.0.1:8888
      name: post-service

jpa옵션 중 hibernate.ddl-auto=create 이 부분은 추후에 validate로 변경하도록 하겠습니다. create는 서버를 구동시키면서 해당 데이터베이스를 리셋시키고 만들겠다는 의미이고, validate는 검증된 데이터베이스이므로 그대로 사용하겠다는 의미입니다.

auth-service와 마찬가지로 git-local-repo 디렉토리에서 post-service.yml파일을 만들어 데이터베이스 정보 설정을 외부로 빼서 관리하도록 하겠습니다.

  • git-local-repo - post-service.yml
datasource:
    url: "jdbc:mariadb://localhost:3306/POSTSERVICE?useSSL=false&characterEncod>
    username: biuea
    password: '{cipher}AQA0kR480WB9lTIJ8iVxdL6PszDEQpyEgtiG2FJeAbM1xKj5dlPq0spD>
    driver-class-name: org.mariadb.jdbc.Driver
  • PostServiceApplication
package com.microservices.postservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class PostServiceApplication {

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

}
  • pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

이 정도로 대략적인 설정들을 마치도록 하고, controller를 만들어 rest api경로를 만들고 이를 바탕으로 테스트를 통과하기 위한 코드들을 작성하도록 하겠습니다.

  • PostController
package com.microservices.postservice.controller;

import com.microservices.postservice.dto.CommentDto;
import com.microservices.postservice.dto.PostDto;
import com.microservices.postservice.service.CommentService;
import com.microservices.postservice.service.PostService;
import com.microservices.postservice.vo.RequestCreateComment;
import com.microservices.postservice.vo.RequestWrite;
import com.microservices.postservice.vo.ResponsePost;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/")
@Slf4j
public class PostController {
    private PostService postService;
    private CommentService commentService;
    private Environment env;

    @Autowired
    public PostController(
        PostService postService,
        CommentService commentService,
        Environment env
    ) {
        this.postService = postService;
        this.commentService = commentService;
        this.env = env;
    }

    @GetMapping("/health_check")
    public String status() {
        return String.format(
        "It's working in Post Service"
            + ", port(local.server.port) =" + env.getProperty("local.server.port")
            + ", port(server.port) =" + env.getProperty("server.port")
        );
    }

    // Using by RequestCreate class, Write Post
    @PostMapping("/write")
    public ResponseEntity<?> write(@RequestBody RequestWrite postVo) throws Exception {
        log.info("Post Service's Controller Layer :: Call write Method!");
        
        PostDto postDto = PostDto.builder()
                         	 .postType(postVo.getPostType())
                             	 .title(postVo.getTitle())
                                 .content(postVo.getContent())
                                 .startDate(postVo.getDate().get(0))
                                 .endDate(postVo.getDate().get(1))
                                 .rentalPrice(postVo.getRentalPrice())
                                 .writer(postVo.getWriter())
                                 .userId(postVo.getUserId())

        PostDto post = postService.write(postDto);
        ResponsePost result = ResponsePost.builder()
                                          .id(post.getId())
                                          .postType(post.getPostType())
                                          .title(post.getTitle())
                                          .content(post.getContent())
                                          .rentalPrice(post.getRentalPrice())
                                          .startDate(post.getStartDate())
                                          .endDate(post.getEndDate())
                                          .createdAt(post.getCreatedAt())
                                          .writer(post.getWriter())
                                          .userId(post.getUserId())
//                                        .images(post.getImages())
                                          .comments(post.getComments())
                                          .status(post.getStatus())
                                          .build();

        return ResponseEntity.status(HttpStatus.CREATED).body(result);
    }

    @GetMapping("/{id}/post")
    public ResponseEntity<?> readPostById(@PathVariable("id") Long id) {
        log.info("Post Service's Controller Layer :: Call readPostById Method!");

        PostDto post = postService.readPostById(id);

        return ResponseEntity.status(HttpStatus.OK).body(ResponsePost.builder()
                                                                     .id(post.getId())
                                                                     .postType(post.getPostType())
                                                                     .title(post.getTitle())
                                                                     .content(post.getContent())
                                                                     .rentalPrice(post.getRentalPrice())
                                                                     .startDate(post.getStartDate())
                                                                     .endDate(post.getEndDate())
                                                                     .createdAt(post.getCreatedAt())
                                                                     .writer(post.getWriter())
                                                                     .userId(post.getUserId())
//                                                                     .images(post.getImages())
                                                                     .comments(post.getComments())
                                                                     .status(post.getStatus())
                                                                     .build());
    }

    // Get All Posts
    @GetMapping("/")
    public ResponseEntity<?> getAllPosts() {
        log.info("Post Service's Controller Layer :: Call getAllPosts Method!");

        Iterable<PostDto> postList = postService.getAllPosts();
        List<ResponsePost> result = new ArrayList<>();

        postList.forEach(post -> {
            result.add(
                ResponsePost.builder()
                            .id(post.getId())
                            .postType(post.getPostType())
                            .title(post.getTitle())
                            .content(post.getContent())
                            .rentalPrice(post.getRentalPrice())
                            .startDate(post.getStartDate())
                            .endDate(post.getEndDate())
                            .createdAt(post.getCreatedAt())
                            .writer(post.getWriter())
                            .userId(post.getUserId())
                            .status(post.getStatus())
//                            .images(post.getImages())
                            .comments(post.getComments())
                            .build()
                );
            }
        );

        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @GetMapping("/{status}")
    public ResponseEntity<?> getAllPostsByStatus(@PathVariable("status") String status) {
        log.info("Post Service's Controller Layer :: Call getAllPostsByStatus Method!");

        Iterable<PostDto> postList = postService.getAllPostsByStatus(status);
        List<ResponsePost> result = new ArrayList<>();

        postList.forEach(post -> {
                result.add(ResponsePost.builder()
                                       .id(post.getId())
                                       .postType(post.getPostType())
                                       .title(post.getTitle())
                                       .content(post.getContent())
                                       .rentalPrice(post.getRentalPrice())
                                       .startDate(post.getStartDate())
                                       .endDate(post.getEndDate())
                                       .createdAt(post.getCreatedAt())
                                       .writer(post.getWriter())
                                       .userId(post.getUserId())
                                       .status(post.getStatus())
    //                                .images(post.getImages())
                                       .comments(post.getComments())
                                       .build());
            }
        );

        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

    @GetMapping("/{userId}/posts")
    public ResponseEntity<?> getPostsByUserId(@PathVariable("userId") String userId) {
        log.info("Post Service's Controller Layer :: Call getPostsByUserId Method!");

        Iterable<PostDto> postList = postService.getPostsByUserId(userId);
        List<ResponsePost> result = new ArrayList<>();

        postList.forEach(post -> {
            result.add(ResponsePost.builder()
                                   .id(post.getId())
                                   .postType(post.getPostType())
                                   .title(post.getTitle())
                                   .content(post.getContent())
                                   .rentalPrice(post.getRentalPrice())
                                   .startDate(post.getStartDate())
                                   .endDate(post.getEndDate())
                                   .createdAt(post.getCreatedAt())
                                   .writer(post.getWriter())
                                   .userId(post.getUserId())
                                   .status(post.getStatus())
    //                            .images(post.getImages())
                                   .comments(post.getComments())
                                   .build());
            }
        );

        return ResponseEntity.status(HttpStatus.OK).body(result);
    }

//    @PostMapping("/rental")
//    public ResponseEntity<?> rental(@RequestBody RequestRental postVo) {
//        log.info("Post Service's Controller Layer :: Call rental Method!");
//
//        return ResponseEntity.status(HttpStatus.OK)
//                             .body(postService.rental(postVo));
//    }

    @PostMapping("/{id}/delete")
    public ResponseEntity<?> deletePost(@PathVariable("id") Long id) {
        log.info("Post Service's Controller Layer :: Call deletePost Method!");

        return ResponseEntity.status(HttpStatus.OK).body(postService.deletePost(id));
    }

    @PostMapping("/{postId}/comments")
    public ResponseEntity<?> writeComment(
        @PathVariable("postId") Long postId,
        @RequestBody RequestCreateComment comment
    ) {
        log.info("Post Service's Controller Layer :: Call writeComment Method!");

        return ResponseEntity.status(HttpStatus.CREATED).body(commentService.writeComment(CommentDto.builder()
                                                                                                    .postId(postId)
                                                                                                    .writer(comment.getWriter())
                                                                                                    .comment(comment.getComment())
                                                                                                    .build()));
    }

    @DeleteMapping("/{id}/comments")
    public ResponseEntity<?> deleteComment(@PathVariable("id") Long id) {
        log.info("Post Service's Controller Layer :: Call deleteComment Method!");

        return ResponseEntity.status(HttpStatus.OK).body(commentService.deleteComment(id));
    }
}

PostController를 작성하면서 메서드의 이름을 알기 쉽게 작성해놓았고, rental이라는 메서드는 우선 주석처리를 해놓겠습니다. 이 메서드는 추후에 kafka를 도입하여 order시 데이터 동기화할 때 사용할 메서드입니다.

이를 바탕으로 vo, dto, entity클래스를 작성하도록 하겠습니다.

  • RequestWrite
package com.microservices.postservice.vo;

import lombok.Getter;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;

@Getter
public class RequestWrite {
    @NotNull(message="postType cannot be null")
    private String postType;

    @NotNull(message="title cannot be null")
    @Size(min=10, message="title cannot be less than 10 characters")
    private String title;

    @NotNull(message="content cannot be null")
    @Size(min=10, message="content cannot be less than 10 characters")
    private String content;

    private Long rentalPrice;

    private List<String> date;

    @NotNull(message="writer cannot be null")
    private String writer;

    @NotNull(message="userId cannot be null")
    private String userId;

    List<MultipartFile> images;
}
  • ResponsePost
package com.microservices.postservice.vo;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.microservices.postservice.entity.CommentEntity;
import lombok.Builder;
import lombok.Data;

import java.util.List;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponsePost {
    private Long id;
    private String userId;
    private String postType;
    private Long rentalPrice;
    private String title;
    private String content;
    private String startDate;
    private String endDate;
    private String createdAt;
    private String writer;
    private String status;
//    private List<ImageEntity> images;
    private List<CommentEntity> comments;

    @Builder
    public ResponsePost(
        Long id,
        String userId,
        String postType,
        Long rentalPrice,
        String title,
        String content,
        String startDate,
        String endDate,
        String createdAt,
        String writer,
        String status,
//        List<ImageEntity> images,
        List<CommentEntity> comments
    ) {
        this.id = id;
        this.userId = userId;
        this.postType = postType;
        this.rentalPrice = rentalPrice;
        this.title = title;
        this.content = content;
        this.startDate = startDate;
        this.endDate = endDate;
        this.createdAt = createdAt;
        this.writer = writer;
        this.status = status;
//        this.images = images;
        this.comments = comments;
    }
}
  • PostDto
package com.microservices.postservice.dto;

import com.microservices.postservice.entity.CommentEntity;
import com.microservices.postservice.entity.ImageEntity;
import lombok.Builder;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Data
public class PostDto {
    private Long id;
    private String userId;
    private String postType;
    private Long rentalPrice;
    private String title;
    private String content;
    private String startDate;
    private String endDate;
    private String createdAt;
    private String writer;
    private String status;
    private List<MultipartFile> multipartFiles;
    private List<CommentEntity> comments;
    private List<ImageEntity> images;

    @Builder
    public PostDto(
        Long id,
        String userId,
        String postType,
        Long rentalPrice,
        String title,
        String content,
        String startDate,
        String endDate,
        String createdAt,
        String writer,
        String status,
        List<MultipartFile> multipartFiles,
        List<CommentEntity> comments,
        List<ImageEntity> images
    ) {
        this.id = id;
        this.userId = userId;
        this.postType = postType;
        this.rentalPrice = rentalPrice;
        this.title = title;
        this.content = content;
        this.startDate = startDate;
        this.endDate = endDate;
        this.createdAt = createdAt;
        this.writer = writer;
        this.status = status;
        this.multipartFiles = multipartFiles;
        this.images = images;
        this.comments = comments;
    }
}
  • PostEntity
package com.microservices.postservice.entity;

import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Data
@Entity
@Table(name="posts")
@NoArgsConstructor
public class PostEntity {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="post_id")
    private Long id;

    @Column(nullable = false)
    private String userId;

    @Column(nullable = false)
    private String postType;

    @Column(nullable = true)
    private Long rentalPrice;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @Column(nullable = true)
    private String startDate;

    @Column(nullable = true)
    private String endDate;

    @Column(nullable = false)
    private String createdAt;

    @Column(nullable = false)
    private String writer;

    @Column(nullable = false)
    private String status;

    @OneToMany(
        mappedBy = "post",
        cascade = { CascadeType.PERSIST, CascadeType.REMOVE },
        orphanRemoval = true
    )
    private List<ImageEntity> images = new ArrayList<>();

    @OneToMany(
        mappedBy = "post",
        cascade = { CascadeType.PERSIST, CascadeType.REMOVE },
        orphanRemoval = true
    )
    private List<CommentEntity> comments = new ArrayList<>();

    @Builder
    public PostEntity(
        Long id,
        String userId,
        String postType,
        Long rentalPrice,
        String title,
        String content,
        String startDate,
        String endDate,
        String createdAt,
        String writer,
        String status
    ) {
        this.id = id;
        this.userId = userId;
        this.postType = postType;
        this.rentalPrice = rentalPrice;
        this.title = title;
        this.content = content;
        this.startDate = startDate;
        this.endDate = endDate;
        this.createdAt = createdAt;
        this.writer = writer;
        this.status = status;
    }

    public void addImage(ImageEntity image) {
        this.images.add(image);

        if(image.getPost() != this) {
            image.setPost(this);
        }
    }

    public void addComment(CommentEntity comment) {
        this.comments.add(comment);

        if(comment.getPost() != this) {
            comment.setPost(this);
        }
    }
}
  • ImageEntity
package com.microservices.postservice.entity;

import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Data
@NoArgsConstructor
@Entity
@Table(name="images")
public class ImageEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="image_id")
    private Long id;

    @ManyToOne
    @JoinColumn(name="post_id")
    private PostEntity post;

    @Column(nullable = false)
    private String orgFilename;

    @Column(nullable = false)
    private String fileName;

    @Column(nullable = false)
    private String filePath;

    private Long fileSize;

    @Builder
    public ImageEntity(
        Long id,
        PostEntity post,
        String orgFilename,
        String filePath,
        String fileName,
        Long fileSize
    ) {
        this.id = id;
        this.post = post;
        this.orgFilename = orgFilename;
        this.filePath = filePath;
        this.fileName = fileName;
        this.fileSize = fileSize;
    }

    public void setPost(PostEntity post) {
        this.post = post;

        if(!post.getImages().contains(this)) {
            post.getImages().add(this);
        }
    }
}
  • CommentEntity
package com.microservices.postservice.entity;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Data
@Entity
@Table(name="comments")
@NoArgsConstructor
public class CommentEntity {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="comment_id")
    Long id;

    @Column(nullable = false)
    String writer;

    @Column(nullable = false)
    String comment;

    @Column(nullable = false)
    String createdAt;

    @ManyToOne
    @JoinColumn(name="post_id")
    private PostEntity post;

    @Builder
    public CommentEntity(
        Long id,
        String writer,
        String comment,
        String createdAt,
        PostEntity post
    ) {
        this.id = id;
        this.writer = writer;
        this.comment = comment;
        this.createdAt = createdAt;
        this.post = post;
    }

    public void setPost(PostEntity post) {
        this.post = post;

        if(!post.getComments().contains(this)) {
            post.getComments().add(this);
        }
    }
}

jpa에서는 일대다 매핑을 사용하기 위해선 1이 되는 entity는 OneToMany어노테이션을, 다가 되는 entity는 ManyToOne어노테이션을 이용합니다. 이를 이용해서 post 테이블과 image, comment테이블들을 매핑해주도록 하겠습니다.

이로써 어느 정도 컨트롤러에 대한 부분은 구현이 완료되었고 서비스 부분은 다음 포스트에서 구현해보도록 하겠습니다. 서비스는 auth-service때와 같이 추상 Service인터페이스를 만들고 ServiceImpl 클래스를 만들어 구체적인 코드를 구현하도록 하겠습니다.

좋은 웹페이지 즐겨찾기