Rental Application (React & Spring boot Microservice) - 21 : message-service

yellow_note·2021년 9월 4일
0

#1 message-service

유저 간 대여를 위해 대화를 해야할 경우 게시글의 댓글을 이용할 수 있고, 메시지 서비스를 이용할 수 있습니다. 그래서 이번 포스트는 유저 간의 대화를 위한 메시지 서비스를 구현해보도록 하겠습니다.

1) sender: 송신자 컬럼입니다.
2) receiver: 수신자 컬럼입니다.
3) content: 메시지 내용입니다.
4) created_at: 메시지 송신 날짜입니다.

#2 프로젝트 생성


다음의 디펜던시를 추가하도록 하겠습니다.

  • 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>
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
	<version>2.2.8.RELEASE</version>
</dependency>
  • application.yml
server:
  port: ${port:7300}

spring:
  application:
    name: message-service

  zipkin:
    base-url: http://127.0.0.1:9411
    enabled: true

  sleuth:
    sampler:
      probability: 1.0

  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: message-service

그리고 git-local-repo로 들어가 message-service.yml파일을 작성하도록 하겠습니다.

  • message-service.yml
spring:
    datasource:
        url: "jdbc:mariadb://localhost:3306/MESSAGESERVICE?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC"
        username: biuea
        password: '{cipher}AQCEYMoeGRLrZ7Pmx2fVif4LO6hPG5Ky06LAUzIURHexEpbng9+L/OA4ld1mL6/LSUOVUI8LMDLTxJTNH/Q4E8eUyAtqXSSXvi3nvEFRc1SiJVGxYUgwY73qXWPjGfhcbHA9KyvAaWBE+jo5ZrjBXkhSEc3mKQm8VfEQ1L3vNbLdbPxnBtXpc60DkY8SXozJO9BPngAAwKpc/RQaDaH07kNrq3objdOfvfdIVg/4Aj/KD5z2baWV5TaMOMB+k3XkMUCnNXBp7rvlH46agY0SXtLUTx6HlvfyZ3ZmXxsOs5cs4g6Qacu6aPvFC/nUu9BT/gj9Ufv32nDIrHyeSg1jbSHZAgXjSxeNPxG9qPTVy2Lkh/OwDPPmg3pBAJKBAXogViA='
        driver-class-name: org.mariadb.jdbc.Driver

gateway:
    ip: 127.0.0.1

전반적인 설정을 마쳤으니 컨트롤러를 작성하도록 하겠습니다.

  • /controller/MessageController
package com.microservices.messageservice.controller;

import com.microservices.messageservice.dto.MessageDto;
import com.microservices.messageservice.service.MessageService;
import com.microservices.messageservice.vo.RequestSend;
import com.microservices.messageservice.vo.ResponseMessage;
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 MessageController {
    private MessageService messageService;
    private Environment env;

    @Autowired
    public MessageController(
        MessageService messageService,
        Environment env
    ) {
        this.messageService = messageService;
        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")
        );
    }

    @PostMapping("/send")
    public ResponseEntity<?> send(@RequestBody RequestSend vo) {
        log.info("Message Service's Controller Layer :: Call send Method!");

        MessageDto messageDto = MessageDto.builder()
                                          .sender(vo.getSender())
                                          .receiver(vo.getReceiver())
                                          .content(vo.getContent())
                                          .build();

        return ResponseEntity.status(HttpStatus.CREATED).body("Successfully send message to " + messageService.send(messageDto).getReceiver());
    }

    @GetMapping("/{id}/get-message")
    public ResponseEntity<?> getMessageById(@PathVariable("id") Long id) {
        log.info("Message Service's Controller Layer :: getMessageById send Method!");

        MessageDto messageDto = messageService.getMessageById(id);

        return ResponseEntity.status(HttpStatus.OK).body(ResponseMessage.builder()
                                                                        .sender(messageDto.getSender())
                                                                        .receiver(messageDto.getReceiver())
                                                                        .content(messageDto.getContent())
                                                                        .createdAt(messageDto.getCreatedAt())
                                                                        .build());
    }

    @GetMapping("/{nickname}/send-list")
    public ResponseEntity<?> getAllSendList(@PathVariable("nickname") String nickname) {
        log.info("Message Service's Controller Layer :: getSendList send Method!");

        Iterable<MessageDto> messageList = messageService.getAllSendList(nickname);
        List<ResponseMessage> messages = new ArrayList<>();

        messageList.forEach(message -> {
            messages.add(ResponseMessage.builder()
                                        .id(message.getId())
                                        .sender(message.getSender())
                                        .receiver(message.getReceiver())
                                        .content(message.getContent())
                                        .createdAt(message.getCreatedAt())
                                        .build());
        });

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

    @GetMapping("/{nickname}/receive-list")
    public ResponseEntity<?> getAllReceiveList(@PathVariable("nickname") String nickname) {
        log.info("Message Service's Controller Layer :: getAllReceiveList send Method!");

        Iterable<MessageDto> messageList = messageService.getAllReceiveList(nickname);
        List<ResponseMessage> messages = new ArrayList<>();

        messageList.forEach(message -> {
            messages.add(ResponseMessage.builder()
                                        .id(message.getId())
                                        .sender(message.getSender())
                                        .receiver(message.getReceiver())
                                        .content(message.getContent())
                                        .createdAt(message.getCreatedAt())
                                        .build());
        });

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

    @PostMapping("/{id}/delete-message")
    public ResponseEntity<?> deleteMessage(@PathVariable("id") Long id) {
        log.info("Message Service's Controller Layer :: deleteMessage send Method!");

        messageService.deleteMessage(id);

        return ResponseEntity.status(HttpStatus.OK).body("Successfully delete the message!");
    }
}

작성한 컨트롤러를 기반으로 코드를 작성하도록 하겠습니다.

  • /vo/RequestSend
package com.microservices.messageservice.vo;

import lombok.Getter;

import javax.validation.constraints.NotNull;

@Getter
public class RequestSend {
    @NotNull(message="Sender cannot be null")
    private String sender;

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

    @NotNull(message="Content cannot be null")
    private String content;
}
  • /vo/ResponseMessage
package com.microservices.messageservice.vo;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder;
import lombok.Data;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseMessage {
    private Long id;
    private String sender;
    private String receiver;
    private String content;
    private String createdAt;

    @Builder
    public ResponseMessage(
        Long id,
        String sender,
        String receiver,
        String content,
        String createdAt
    ) {
        this.id = id;
        this.sender = sender;
        this.receiver = receiver;
        this.content = content;
        this.createdAt = createdAt;
    }
}
  • /dto/MessageDto
package com.microservices.messageservice.dto;

import lombok.Builder;
import lombok.Data;

@Data
public class MessageDto {
    private Long id;
    private String sender;
    private String receiver;
    private String content;
    private String createdAt;
    private String status;

    @Builder
    public MessageDto(
        Long id,
        String sender,
        String receiver,
        String content,
        String createdAt,
        String status
    ) {
        this.id = id;
        this.sender = sender;
        this.receiver = receiver;
        this.content = content;
        this.createdAt = createdAt;
        this.status = status;
    }
}
  • /entity/MessageEntity
package com.microservices.messageservice.entity;

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

import javax.persistence.*;

@Data
@Entity
@Table(name="messages")
@NoArgsConstructor
public class MessageEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String sender;

    @Column(nullable = false)
    private String receiver;

    @Column(nullable = false)
    private String content;

    @Column(nullable = false)
    private String createdAt;

    @Column(nullable = false)
    private String status;

    @Builder
    public MessageEntity(
        Long id,
        String sender,
        String receiver,
        String content,
        String createdAt,
        String status
    ) {
        this.id = id;
        this.sender = sender;
        this.receiver = receiver;
        this.content = content;
        this.createdAt = createdAt;
        this.status = status;
    }
}

#3 MessageService

MessageService 클래스를 작성하도록 하겠습니다.

  • MessageService
package com.microservices.messageservice.service;

import com.microservices.messageservice.dto.MessageDto;

public interface MessageService {
    MessageDto send(MessageDto dto);

    MessageDto getMessageById(Long id);

    Iterable<MessageDto> getAllSendList(String sender);

    Iterable<MessageDto> getAllReceiveList(String receiver);

    void deleteMessage(Long id);
}

MessageService는 총 6개의 메서드를 가지고 있습니다.

1) send: message를 상대방에게 보내는 메서드입니다.
2) getMessageById: id값을 이용하여 메시지 상세정보를 가져오는 메서드입니다.
3) getAllSendList: 송신리스트를 위한 메서드입니다.
4) getAllReceiveList: 수신리스트를 위한 메서드입니다.
5) deleteMessage: 메시지를 삭제하기 위한 메서드인데 실제 삭제가 아닌 메시지의 상태값을 DELETE_MESSAGE로 변경해주는 메서드입니다.

  • MessageServiceImpl
package com.microservices.messageservice.service;

import com.microservices.messageservice.dto.MessageDto;
import com.microservices.messageservice.entity.MessageEntity;
import com.microservices.messageservice.repository.MessageRepository;
import com.microservices.messageservice.util.DateUtil;
import com.microservices.messageservice.vo.ResponseMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class MessageServiceImpl implements MessageService {
    private MessageRepository messageRepository;

    @Autowired
    public MessageServiceImpl(MessageRepository messageRepository) {
        this.messageRepository = messageRepository;
    }

    @Transactional
    @Override
    public MessageDto send(MessageDto dto) {
        log.info("Message Service's Service Layer :: Call send Method!");

        MessageEntity messageEntity = MessageEntity.builder()
                                                   .sender(dto.getSender())
                                                   .receiver(dto.getReceiver())
                                                   .content(dto.getContent())
                                                   .createdAt(DateUtil.dateNow())
                                                   .status("CREATE_MESSAGE")
                                                   .build();

        messageRepository.save(messageEntity);

        return MessageDto.builder()
                         .sender(dto.getSender())
                         .receiver(dto.getReceiver())
                         .content(dto.getContent())
                         .createdAt(DateUtil.dateNow())
                         .status("CREATE_MESSAGE")
                         .build();
    }

    @Transactional
    @Override
    public MessageDto getMessageById(Long id) {
        log.info("Message Service's Service Layer :: Call getMessageById Method!");

        MessageEntity messageEntity = messageRepository.findMessageById(id);

        return MessageDto.builder()
                         .id(messageEntity.getId())
                         .sender(messageEntity.getSender())
                         .receiver(messageEntity.getReceiver())
                         .content(messageEntity.getContent())
                         .createdAt(messageEntity.getCreatedAt())
                         .build();
    }

    @Transactional
    @Override
    public Iterable<MessageDto> getAllSendList(String sender) {
        log.info("Message Service's Service Layer :: Call getAllSendList Method!");

        Iterable<MessageEntity> messageList = messageRepository.findAllSendList(sender);
        List<MessageDto> messages = new ArrayList<>();

        messageList.forEach(message -> {
            messages.add(MessageDto.builder()
                                   .id(message.getId())
                                   .sender(message.getSender())
                                   .receiver(message.getReceiver())
                                   .content(message.getContent())
                                   .createdAt(message.getCreatedAt())
                                   .build());
        });

        return messages;
    }

    @Transactional
    @Override
    public Iterable<MessageDto> getAllReceiveList(String receiver) {
        log.info("Message Service's Service Layer :: Call getAllReceiveList Method!");

        Iterable<MessageEntity> messageList = messageRepository.findAllReceiveList(receiver);
        List<MessageDto> messages = new ArrayList<>();

        messageList.forEach(message -> {
            messages.add(MessageDto.builder()
                                   .id(message.getId())
                                   .sender(message.getSender())
                                   .receiver(message.getReceiver())
                                   .content(message.getContent())
                                   .createdAt(message.getCreatedAt())
                                   .build());
        });

        return messages;
    }

    @Transactional
    @Override
    public void deleteMessage(Long id) {
        log.info("Message Service's Service Layer :: Call deleteMessage Method!");

        messageRepository.updateStatus(id);
    }
}

jpa에서 기본으로 제공해주는 쿼리 관련 메시지를 이용할 수도 있지만 다음과 같이 직접 쿼리문을 사용해서 데이터의 crud를 조작할 수 있습니다.

  • MessageRepository
package com.microservices.messageservice.repository;

import com.microservices.messageservice.entity.MessageEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface MessageRepository extends JpaRepository<MessageEntity, Long> {

    MessageEntity findMessageById(Long id);

    @Query(
        value = "SELECT * " +
                "FROM messages m " +
                "WHERE m.status = 'CREATE_MESSAGE' AND m.sender = :sender",
        nativeQuery = true
    )
    Iterable<MessageEntity> findAllSendList(String sender);

    @Query(
        value = "SELECT * " +
                "FROM messages m " +
                "WHERE m.status = 'CREATE_MESSAGE' AND m.receiver = :receiver",
        nativeQuery = true
    )
    Iterable<MessageEntity> findAllReceiveList(String receiver);

    @Query(
        value = "UPDATE messages m " +
                "SET m.status = 'DELETE_MESSAGE' " +
                "WHERE m.id = :id",
        nativeQuery = true
    )
    void updateStatus(Long id);
}

쿼리 어노테이션은 @Query(value=SQL, nativeQuery(boolean)으로 사용할 수 있습니다. 예를 들어 @Query( value = "SELECT * " + "FROM messages m " + "WHERE m.status = CREATE_MESSAGE AND m.receiver = :receiver", nativeQuery = true ) 의 어노테이션은 다음의 뜻을 가집니다.

SELET * => 해당 컬럼의 데이터의 값을 모두 가져옵니다.
FROM messages m => messages 테이블에 m 이라는 별칭을 정해주고 messages 테이블로부터
WHERE m.status = 'CREATE_MESSAGE' AND m.receiver = :receiver => m.status의 값이 CREATE_MESSAGE이며, m.receiver의 값이 인자값인 receiver인 조건을 지정합니다.
nativeQuery = true => JPQL이 아닌 SQL로 사용하겠습니다.

그러면 전체적으로 message-service에 대한 코드가 완료되었으니 apigateway-service의 application.yml파일에 message-service관련 endpoint를 설정하고 테스트를 진행해보도록 하겠습니다.

#4 테스트

1) POST /send



우선 test-02에게 메시지를 2개, test-01에게 메시지를 1개 보내는 메서드를 실행했고, 데이터베이스 결과를 보니 잘 나오는 모습을 살펴볼 수 있습니다.

2) GET /:id/getMessage

3) GET /:nickname/send-list

4) GET /:nickname/receive-list

5) POST /:id/delete-message

test-02의 메시지함에서 메시지를 지우는 경우이기 때문에 메시지를 지우고 조회를 요청한 결과 DELETE_MESSAGE를 제외한 메시지리스트가 잘 조회되는 결과를 볼 수 있습니다.

모든 테스트 결과 잘 나오는 모습을 볼 수 있습니다. 여기까지해서 백엔드 마이크로서비스 부분이 전부 완료가 되었습니다. 그러면 이제 리액트로 프론트엔드 서버를 만들어 백엔드와 요청을 해야겠죠. 다음 포스트부터는 리액트로 프론트엔드 서버를 만들어 보도록 하겠습니다.

0개의 댓글