JSCODE Logo
프로그래밍 과외블로그후기멘토진
회사명 : JSCODE대표 : 박재성사업자 등록번호 : 244-22-01557통신판매업 : 제 2023-인천미추홀-0381 호
학원 명칭 : 제이에스코드(JSCODE)원격학원학원설립ㆍ운영 등록번호 : 제6063호

서울특별시 구로구 경인로 20가길 11(오류동, 아델리아)

Copyright ⓒ 2025 JSCODE - 최상위 현업 개발자들의 프로그래밍 교육 All rights reserved.

이용약관개인정보처리방침
← 블로그 목록으로 돌아가기

[실습] ‘외부용 API’와 ‘마이크로서비스간 통신용 API’ 구분하기

JSCODE 박재성
JSCODE 박재성
2025-12-06
author
JSCODE 박재성
category
MSA
createdAt
Dec 6, 2025
series
비전공자도 이해할 수 있는 MSA 입문/실전
slug
practice-separate-public-and-internal-apis
type
post
updatedAt
Dec 6, 2025 05:47 AM

✅ ‘외부용 API’와 ‘마이크로서비스간 통신용 API’ 구분하기

현재까지 프로젝트에서 아래의 API를 구현했다.
[사용자 서비스]
  • 회원가입 (POST /users/sign-up) - 외부용 API
  • 특정 사용자 정보 조회 (GET /users/{userId}) - 내부용 API
  • 여러 사용자 정보 조회 (GET /users) - 내부용 API
  • 활동 점수 적립 (POST /users/activity-score/add) - 내부용 API
  • 활동 점수 차감 (POST /users/activity-score/deduct) - 내부용 API
 
[게시글 서비스]
  • 게시글 생성 (POST /boards) - 외부용 API
  • 특정 게시글 조회 (GET /boards/{boardId}) - 외부용 API
  • 전체 게시글 조회 (GET /boards) - 외부용 API
 
[포인트 서비스]
  • 포인트 적립 (POST /points/add) - 내부용 API
  • 포인트 차감 (POST /points/deduct) - 내부용 API
 
MSA에서는 위와 같이 반드시 외부 클라이언트가 사용할 API와 내부 마이크로서비스끼리 사용하는 API를 구분해야 한다. 그래야 나중에 구분해서 보안 처리를 하기가 쉽다. 한 마디로, 외부 클라이언트가 ‘내부 마이크로서비스끼리 사용하는 API’에 접근하지 못하도록 설정하기가 쉽다.
 
외부 클라이언트용 API 주소와 내부 마이크로서비스용 API를 구분하기 위해 아래와 같이 URL을 구분하자.
  • 외부 클라이언트용 API 주소 : /api/____
  • 내부 클라이언트용 API 주소 : /internal/____
 
 

✅ 사용자 서비스 코드 수정하기

  1. UserController 복사해서 UserInternalController 만들기
    1. controller/UserInternalController
      @RestController @RequestMapping("/internal/users") public class UserInternalController { ... @PostMapping("sign-up") public ResponseEntity<Void> signUp() {...} @GetMapping("{userId}") public ResponseEntity<UserResponseDto> getUser(...) {...} @GetMapping() public ResponseEntity<List<UserResponseDto>> getUsersByIds() {...} @PostMapping("activity-score/add") public ResponseEntity<Void> addActivityScore() {...} }
       
  1. UserController 코드 수정하기
    1. controller/UserController
      @RestController @RequestMapping("/api/users") public class UserController { ... @PostMapping("sign-up") public ResponseEntity<Void> signUp() {...} @GetMapping("{userId}") public ResponseEntity<UserResponseDto> getUser(...) {...} @GetMapping() public ResponseEntity<List<UserResponseDto>> getUsersByIds() {...} @PostMapping("activity-score/add") public ResponseEntity<Void> addActivityScore() {...} }
       
  1. Client 코드 수정하기
    1. client/PointClient
      @Component public class PointClient { ... public void addPoints(Long userId, int amount) { AddPointsRequestDto addPointsRequestDto = new AddPointsRequestDto(userId, amount); this.restClient.post() .uri("/internal/points/add") .contentType(MediaType.APPLICATION_JSON) .body(addPointsRequestDto) .retrieve() .toBodilessEntity(); } }
 
  1. 서버 다시 실행시키기
 
 

✅ 게시글 서비스 코드 수정하기

게시글 서비스는 외부용 API만 존재한다.
  1. BoardController 코드 수정하기
    1. controller/BoardController
      @RestController @RequestMapping("/api/boards") public class BoardInternalController { ... @PostMapping public ResponseEntity<Void> create(...) {...} @GetMapping("/{boardId}") public ResponseEntity<BoardResponseDto> getBoard(...) {...} @GetMapping() public ResponseEntity<List<BoardResponseDto>> getBoards() {...} }
       
  1. Client 코드 수정하기
    1. client/PointClient
      @Component public class PointClient { ... public void deductPoints(Long userId, int amount) { DeductPointsRequestDto deductPointsRequestDto = new DeductPointsRequestDto(userId, amount); this.restClient.post() .uri("/internal/points/deduct") .contentType(MediaType.APPLICATION_JSON) .body(deductPointsRequestDto) .retrieve() .toBodilessEntity(); } public void addPoints(Long userId, int amount) { AddPointsRequestDto addPointsRequestDto = new AddPointsRequestDto(userId, amount); this.restClient.post() .uri("/internal/points/add") .contentType(MediaType.APPLICATION_JSON) .body(addPointsRequestDto) .retrieve() .toBodilessEntity(); } }
       
      client/UserClient
      @Component public class UserClient { ... public Optional<UserResponseDto> fetchUser(Long userId) { try { UserResponseDto userResponseDto = this.restClient.get() .uri("/internal/users/{userId}", userId) .retrieve() .body(UserResponseDto.class); return Optional.ofNullable(userResponseDto); } catch (RestClientException e) { // 로깅 : 예외 발생 시 로그를 남겨 문제를 파악할 수 있게 해야 함 // log.error("사용자 정보 조회 실패: {}", e.getMessage(), e); return Optional.empty(); } } public List<UserResponseDto> fetchUsersByIds(List<Long> ids) { try { return this.restClient.get() .uri(uriBuilder -> uriBuilder .path("/internal/users") .queryParam("ids", ids) .build() ) .retrieve() .body(new ParameterizedTypeReference<>() {}); } catch (RestClientException e) { // 로깅 : 예외 발생 시 로그를 남겨 문제를 파악할 수 있게 해야 함 // log.error(...) return Collections.emptyList(); } } public void addActivityScore(Long userId, int score) { AddActivityScoreRequestDto addActivityScoreRequestDto = new AddActivityScoreRequestDto(userId, score); this.restClient.post() .uri("/internal/users/activity-score/add") .contentType(MediaType.APPLICATION_JSON) .body(addActivityScoreRequestDto) .retrieve() .toBodilessEntity(); } }
 
  1. 서버 다시 실행시키기
 

✅ 포인트 서비스 코드 수정하기

포인트 서비스는 내부용 API만 존재한다.
  1. PointController를 PointInternalController로 이름 바꾸기
    1. @RestController @RequestMapping("/points") public class PointInternalController { ... }
       
  1. PointInternalController로 코드 수정하기
    1. @RestController @RequestMapping("/internal/points") public class PointInternalController { ... }
       
  1. 서버 다시 실행시키기
 
 

✅ 잘 작동하는 지 테스트하기

클라이언트가 사용하는 API에 통신을 보내서 성공 응답이 날라오는 지 확인해보자.
  1. 회원가입 API
    1. notion image
       
  1. 게시글 작성 API
    1. notion image
       
  1. 게시글 전체 조회 API
    1. notion image
       
  1. 특정 게시글 조회 API
    1. notion image
 
author
JSCODE 박재성
category
MSA
createdAt
Dec 6, 2025
series
비전공자도 이해할 수 있는 MSA 입문/실전
slug
type
series-footer
updatedAt
Dec 6, 2025 05:45 AM
📎
이 글은 비전공자도 이해할 수 있는 MSA 입문/실전 강의의 수업 자료 중 일부입니다.