MSA

[MSA] Part. 12 Catalog Service

cwchoiit 2024. 1. 8. 10:44
728x90
반응형
SMALL

이제 새로운 마이크로 서비스인 CatalogService를 만들자. 이 서비스는 상품에 대한 데이터를 관리하는 서비스이다.

상품명, 상품아이디, 수량, 단일가격 등에 대한 정보를 가지고 있는 엔티티를 가지고 있을 것이고 그 엔티티에 대한 관리가 일어나는 서비스이다.

728x90
SMALL

 

우선, 스프링 프로젝트를 만들어야 하는데 Spring Initializer를 사용하는 방법은 기존 포스팅에 작성해 두었으니 dependencies부터 시작하자.

 

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'
}

group = 'springmsa'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '21'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2023.0.0")
}

dependencies {
    implementation 'org.modelmapper:modelmapper:3.1.1'

    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

 

필요한 dependencies는 다음과 같다.

  • Lombok
  • Spring Data JPA
  • Spring Web
  • Eureka Client
  • Spring Boot Devtools
  • H2
  • ModelMapper

application.yml

server:
  port: 0

spring:
  application:
    name: catalog-service
  jpa:
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        format_sql: true
        show_sql: true
        default_batch_fetch_size: 500
    defer-datasource-initialization: true
  sql:
    init:
      mode: always

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

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true # Eureka Server로부터 Eureka Server에 등록된 다른 인스턴스의 정보를 주기적으로 갱신하는 옵션
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.cloud.client.hostname}:${spring.application.instance_id:${random.value}}

 

여기서는 서버를 띄울 때 데이터베이스에 초기데이터를 만들어주기 위해 spring.jpa.defer-datasource-initialization: true 속성과 spring.sql.init.mode: always 속성을 추가했다. 이는 resources 폴더에 data.sql 파일이 있을 때 해당 파일을 보고 초기데이터를 넣어주기 위함이다.

 

data.sql (resources)

insert into catalog(product_id, product_name, stock, unit_price) values ('CATALOG-001', 'Berlin', 100, 1500);
insert into catalog(product_id, product_name, stock, unit_price) values ('CATALOG-002', 'Tokyo', 200, 1000);
insert into catalog(product_id, product_name, stock, unit_price) values ('CATALOG-003', 'Stockholm', 300, 2500);

 

 

 

Entity

BaseEntity

package springmsa.springmsacatalogservice.entity;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

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

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

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

 

Catalog

package springmsa.springmsacatalogservice.entity;

import jakarta.persistence.*;
import lombok.Data;


@Data
@Entity
public class Catalog extends BaseEntity {

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

    @Column(nullable = false, length = 120, unique = true)
    private String productId;

    @Column(nullable = false)
    private String productName;

    @Column(nullable = false)
    private Integer stock;

    @Column(nullable = false)
    private Integer unitPrice;
}

 

Repository

 

CatalogRepository

package springmsa.springmsacatalogservice.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import springmsa.springmsacatalogservice.entity.Catalog;

@Repository
public interface CatalogRepository extends JpaRepository<Catalog, Long> {
    Catalog findByProductId(String productId);
}

 

 

Service

CatalogService

package springmsa.springmsacatalogservice.service;

import springmsa.springmsacatalogservice.dto.CreateCatalogDto;
import springmsa.springmsacatalogservice.entity.Catalog;

public interface CatalogService {
    Iterable<Catalog> getAllCatalogs();

    Catalog createCatalog(CreateCatalogDto catalogDto);
}

 

인터페이스는 두 개의 메서드가 있다. 전체 조회와 생성.

 

CatalogServiceImpl

package springmsa.springmsacatalogservice.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import springmsa.springmsacatalogservice.dto.CreateCatalogDto;
import springmsa.springmsacatalogservice.entity.Catalog;
import springmsa.springmsacatalogservice.repository.CatalogRepository;

@Slf4j
@RequiredArgsConstructor
@Service
public class CatalogServiceImpl implements CatalogService {

    private final CatalogRepository catalogRepository;
    private final ModelMapper modelMapper;

    @Override
    public Iterable<Catalog> getAllCatalogs() {
        return catalogRepository.findAll();
    }

    @Override
    public Catalog createCatalog(CreateCatalogDto catalogDto) {
        Catalog catalog = modelMapper.map(catalogDto, Catalog.class);
        catalogRepository.save(catalog);

        return catalog;
    }
}

 

 

DTO

CatalogDto

package springmsa.springmsacatalogservice.dto;

import lombok.Data;

@Data
public class CatalogDto {
    private String productId;
    private Integer quantity;
    private Integer unitPrice;
    private Integer totalPrice;
    private String orderId;
    private String userId;
}

 

CreateCatalogDto

package springmsa.springmsacatalogservice.dto;

import lombok.Data;

@Data
public class CreateCatalogDto {
    private String productId;
    private String productName;
    private Integer stock;
    private Integer unitPrice;
}

 

ResponseCatalogDto

package springmsa.springmsacatalogservice.dto;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class ResponseCatalogDto {
    private String productId;
    private String productName;
    private Integer unitPrice;
    private Integer stock;
    private LocalDateTime createdDate;
}

 

ApiResponseDto

package springmsa.springmsacatalogservice.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ApiResponseDto<T> {
    private T data;
    private String errorMessage;
}

 

 

Controller

CatalogController

package springmsa.springmsacatalogservice.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import springmsa.springmsacatalogservice.dto.ApiResponseDto;
import springmsa.springmsacatalogservice.dto.CreateCatalogDto;
import springmsa.springmsacatalogservice.dto.ResponseCatalogDto;
import springmsa.springmsacatalogservice.entity.Catalog;
import springmsa.springmsacatalogservice.service.CatalogService;

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

@Slf4j
@RestController
@RequestMapping("/catalogs")
@RequiredArgsConstructor
public class CatalogController {

    private final CatalogService catalogService;
    private final ModelMapper modelMapper;

    @GetMapping("")
    public ResponseEntity<ApiResponseDto<List<ResponseCatalogDto>>> getCatalogs() {
        Iterable<Catalog> allCatalogs = catalogService.getAllCatalogs();

        List<ResponseCatalogDto> result = new ArrayList<>();

        allCatalogs.forEach(catalog -> {
            ResponseCatalogDto catalogDto = modelMapper.map(catalog, ResponseCatalogDto.class);
            result.add(catalogDto);
        });

        return ResponseEntity.status(HttpStatus.OK).body(new ApiResponseDto<>(result, null));
    }

    @PostMapping("")
    public ResponseEntity<ApiResponseDto<ResponseCatalogDto>> createCatalog(@RequestBody CreateCatalogDto catalogDto) {
        Catalog catalog = catalogService.createCatalog(catalogDto);

        ResponseCatalogDto responseCatalogDto = modelMapper.map(catalog, ResponseCatalogDto.class);

        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(new ApiResponseDto<>(responseCatalogDto, null));
    }
}

 

 

 

마무리

User Service와 구조나, 코드 내용이 상이한게 거의 없기 때문에 코드만 써도 괜찮아 보인다. 전체적인 핵심은 이렇게 도메인 별 MicroService를 만들어서 서비스의 크기를 작게 나누고 API Gateway를 통해 서비스로 접근하는 방식을 고수하고 있다는 점이다. 이 서비스 역시 유레카 서버에 등록되고 API Gateway가 관리하는 서비스이다.

728x90
반응형
LIST