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

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

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

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

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

JSCODE 박재성
JSCODE 박재성
2026. 03. 18.
author
JSCODE 박재성
category
MSA
createdAt
Dec 6, 2025 05:16 AM
isPublic
isPublic
series
비전공자도 이해할 수 있는 MSA 입문/실전
slug
practice-separate-public-and-internal-apis
type
post
updatedAt
Mar 18, 2026 09:00

✅ ‘외부용 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