본문 바로가기

MSA

디스커버리 서버와 유레카 적용 사례

구성: 사용자→게이트웨이→유레카←(이커머스: 회원,주문,재고)←설정 서버,DB서버

1. 프로젝트 생성

 

2. 유레카 클라이언트로 설정

  • UserServiceApplication 설정
package com.example.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {

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

}
  • yml 파일 설정
server:
  port: 0

spring:
  application:
    name: user-service

#유레카 클라이언트-> 디스커버리로 등록 부분
eureka:
  client:
    register-with-eureka: true # 유레카 서버에 등록하기
    fetch-registry: true # 유레카 서버에서 서버 상태 지속적 감시
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka #8761은 유레카 디스커버리 서버 port번호 + /eureka

  # 유레카에 보여지는 표시를 이름값으로 설정
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}

 

3. 게이트웨이 서버에 등록

  • 게이트웨이 서버 yml파일에 routes에 user-service 정보를 기입
server:
  port: 8000

spring:
  application:
    name: apigateway-server
  cloud:
    gateway:
      default-filters:
        - name: GlobalFilter
          args:
            message: Global Filter Default Message Test
            pre: true
            post: true

      routes: # 개별 마이크로서비스 정보를 routes에 기입함
        - id: first-service
          uri: lb://FIRST-SERVICE
          predicates:
            - Path=/first-service/**
          filters:
            - CustomFilter
            - LogFilter

        - id: second-service
          uri: http://localhost:8002/
          predicates:
            - Path=/second-service/**
          filters:
            - CustomFilter2

#        유저 서비스 등록
        - id: user-service
          uri: lb://USER-SERVICE  #직접적인 주소를 등록하지 않고 로드밸런서를 설정해서 디스커버리 유레카를 통한다.
          predicates:
            - Path=/user-service/**

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      default-zone: http://127.0.0.1:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}

 

4. User 기능 개발

  • userController
package com.example.userservice.controller;

import com.example.userservice.domain.User;
import com.example.userservice.dto.RequestCreateUserDto;
import com.example.userservice.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
// MVC 패턴에서 @RequestMapping("user-service")가 없어도 빌드 오류가 발생하지는 않지만
// 게이트웨이서  predicates: - Path=/user-service/** 로 등록되었기 때문에 @RequestMapping("user-service")가 필요함!!
@RequestMapping("user-service")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping("health-check")
    public String healthCheck(){
        return "Server is available!";
    }

    @PostMapping("users")
    public ResponseEntity<?> createUser(@Valid @RequestBody RequestCreateUserDto userDto){
        // @RequestBody가 붙으면 RequestCreateUserDto 맞는 필트를 채울 수 있도록 json으로 보내주겠다
        // @RequestBody가 안 붙으면 폼에서 들어온 것으로 매핑됨
        // @Vaild는 사전의 지정한 양식이 맞는지 검증한 후에 역직렬화를 진행

        userService.creatUser(userDto);
        return ResponseEntity.ok("회원가입완료");
    }

    // 가입된 계정을 uuid 기반으로 찾아올 수 있도록 get 요청을 만들어주세요.
    // users/{uuid}로 조회했을 때 해당하는 User 엔티티 객체를 받아옵니다.

    @GetMapping("users/{uuid}")
    public ResponseEntity<?> findUserByUuid(@PathVariable String uuid){
        User user = userService.findUserByUuid(uuid);
        return ResponseEntity.ok(user);
    }
}
  • UserService
package com.example.userservice.service;

import com.example.userservice.domain.User;
import com.example.userservice.dto.RequestCreateUserDto;
import com.example.userservice.repoaitory.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    // 회원가입
    public void creatUser(RequestCreateUserDto userDto){
        // dto를 entity로 변경해주는 작업이 필요
        User user = userDto.toEntity();
        userRepository.save(user);
    }

    public User findUserByUuid(String uuid){
        User user = userRepository.findUserByUuid(uuid);
        return user;
    }
}
  • UserRepository
package com.example.userservice.repoaitory;

import com.example.userservice.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Long> {

    User findUserByUuid(String uuid);
}
  • RequestCreateUserDto
package com.example.userservice.dto;

// DTO를 사용하여 User 테이블 은닉화
// 네이밍 이유 : 요청/응답 표시 + 어떤 요청(DB)

import com.example.userservice.domain.User;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Size;
import lombok.*;

import java.time.LocalDateTime;
import java.util.UUID;

@Getter @Setter
@NoArgsConstructor @AllArgsConstructor @Builder
public class RequestCreateUserDto {
    @Email
    private String email;

    @NonNull
    @Size(min = 8,max = 20,message = "비밀번호는 최소 8글자이고, 최대 20글자입니다.")
    private String pw;

    @NonNull
    private String name;

    @NonNull
    private String userId;

    // 엔티티로 리턴함으로써 도메인 레이어를 참조
    public User toEntity(){
        return User.builder()
                .emaiil(this.email)
                .endPw(this.pw)
                .userId(this.userId)
                .name(this.name)
                .uuid(UUID.randomUUID().toString())
                .creatAt(LocalDateTime.now())
                .build();
    }
}
  • User
package com.example.userservice.domain;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Getter // @Setter은 엔티티 수정을 막기 위해서, 캡슐화 + 은닉화
@AllArgsConstructor @NoArgsConstructor @Builder
@Table(name="users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    private String emaiil;

    private String name;

    private String userId;

    private String endPw;

    private String uuid;

    private LocalDateTime creatAt;
}

// 도메인 레이어는 다른 레이어를 참조하면 안 되지만 다른 레이어에서 도메인 레이어를 참조해도 된다.

 

추가 설명: UUID란 

UUID(Universally Unique Identifier)는 전역적으로 고유한 식별자를 생성하기 위한 표준입니다.

엔티티식별자를 생성할 때 주의할 점은 같은 시간에 동시에 식별자를 생성해도 같은 식별자가 만들어지면 안 된다는 점입니다.

외에도  일렬번호로 자동 증가 컬럼에 비하면, DB 이전, 마이그레이션 등의 상황에서 삭제 시에도 번호 공백이 없고, 상품품명과 같은 민감한 내부 정보에 있어 수량 예측을 막는 효과도 줄 수 있습니다.

Java에서는 java.util.UUID 클래스를 사용하여 UUID를 쉽게 생성할 수 있습니다.