이제 새로운 마이크로 서비스인 CatalogService를 만들자. 이 서비스는 상품에 대한 데이터를 관리하는 서비스이다.
상품명, 상품아이디, 수량, 단일가격 등에 대한 정보를 가지고 있는 엔티티를 가지고 있을 것이고 그 엔티티에 대한 관리가 일어나는 서비스이다.
우선, 스프링 프로젝트를 만들어야 하는데 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가 관리하는 서비스이다.
'MSA' 카테고리의 다른 글
[MSA] Part 11. User Service (WebSecurity) (2) | 2024.01.08 |
---|---|
[MSA] Part 10. User Service (Find User/s) (0) | 2024.01.08 |
[MSA] Part 9. UserService (Create User) (2) | 2023.10.11 |
[MSA] Part 8. H2 Database 연동 그리고 'ddl-auto' property (0) | 2023.10.11 |
[MSA] Part 7. API Gateway를 Eureka에 등록하기 (0) | 2023.10.10 |