유저 간 대여를 위해 대화를 해야할 경우 게시글의 댓글을 이용할 수 있고, 메시지 서비스를 이용할 수 있습니다. 그래서 이번 포스트는 유저 간의 대화를 위한 메시지 서비스를 구현해보도록 하겠습니다.
1) sender: 송신자 컬럼입니다.
2) receiver: 수신자 컬럼입니다.
3) content: 메시지 내용입니다.
4) created_at: 메시지 송신 날짜입니다.
다음의 디펜던시를 추가하도록 하겠습니다.
<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>
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
spring:
cloud:
config:
uri: http://127.0.0.1:8888
name: message-service
그리고 git-local-repo로 들어가 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
전반적인 설정을 마쳤으니 컨트롤러를 작성하도록 하겠습니다.
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!");
}
}
작성한 컨트롤러를 기반으로 코드를 작성하도록 하겠습니다.
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;
}
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;
}
}
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;
}
}
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;
}
}
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로 변경해주는 메서드입니다.
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를 조작할 수 있습니다.
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를 설정하고 테스트를 진행해보도록 하겠습니다.
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를 제외한 메시지리스트가 잘 조회되는 결과를 볼 수 있습니다.
모든 테스트 결과 잘 나오는 모습을 볼 수 있습니다. 여기까지해서 백엔드 마이크로서비스 부분이 전부 완료가 되었습니다. 그러면 이제 리액트로 프론트엔드 서버를 만들어 백엔드와 요청을 해야겠죠. 다음 포스트부터는 리액트로 프론트엔드 서버를 만들어 보도록 하겠습니다.