MicroService Design Pattern ‐ Service Communications Patterns - woojin-playground/Backend-PlayGround GitHub Wiki
- Request/Response Communication
- HTTP, REST : 동기식 요청/응답 방식, 마이크로서비스 API
- Restful API : HTTP Protocol + Method, GET/POST/PUT/DELETE/PATCH
- gRPC : 내부 마이크로서비스 간 통신이 잦을 때, 고성능 + 낮은 지연 시간
- GraphQL : 요청 시 사용하는 쿼리에 따라 다른 응답, HTTP 요청 회수 및 응답 사이즈 줄임, 필요한 데이터 구조를 정의할 수 있어 유연성과 효율성을 확보할 수 있다.
- Push/Pull Communication
- HTTP, WebSocket 기반의 Push + Realtime 통신
- WebSocket API
- 채팅 애플리케이션에서 많이 활용
- 스트리밍 대시보드 - 양방향
- 클라이언트, 서버 모두 메시지 전송 가능
- 백엔드 서버는 연결된 모든 사용자에게 데이터 푸시 가능
- HTTP, AMQP
- Polling
- 일정 시간마다 새로고침
- 불필요한 통신 발생 가능성 높다.
- 연결을 맺고 끊는데 드는 비용이 크다.
- Event-Driven Communication(비동기)
- 마이크로서비스 간 호출 대신 이벤트 생성(비동기 방식) - Message Broker System에서 메시지를 소비
- AMQP(Advanced Message Queueing Protocol) - Publish/Subscribe 패턴
- 메시지 소비자와 생산자는 서로의 존재를 모르게 설계할 필요가 있다.
- 서비스의 종속성이 감소하면서 느슨한 결합의 서비스를 구성할 수 있다.
- 아키텍처의 복잡성 자체가 증가한다.
- Restful API : 마이크로서비스를 위한 가장 일반적인 통신 아키텍처
- Request/Response API - RESTful API
장점
- 사용 용이(웹 브라우저)
- HTTP 프로토콜
- HTTP GET 캐싱 옵션
- Json 형식
단점
- 관계형 데이터를 얻기 위한 여러 요청 필요하다.
- 많은 데이터를 표현할 때 호출이 많아진다.
- 데이터 크기가 커지게 되면 그에 따라 많은 오버헤드가 발생한다.
- 클라이언트가 필요한 데이터 구조를 정의 → 동일한 데이터 구조가 서버로부터 반환된다.
- 모든 가능한 데이터 스키마 생성이 가능하다.
- 클라이언트에서 쿼리를 요청하면 검증 후에 쿼리를 실행한다.
- API를 위한 쿼리 및 조작 언어 → 기존 데이터를 가지고 쿼리를 수행할 수 있는 방법을 제공한다.
- 클라이언트가 필요한 것을 정확하게 요청하고 얻을 수 있다.
- 단일 요청에서 여러 소스에 접근하여 데이터 가져오기
- 네트워크 호출 감소
- 한 리소스의 속성과 리소스 간의 특정 리소스 참조가 가능하다.
- 애플리케이션에 필요한 데이터만 가져오기가 가능하다.
- 기존 쿼리에 영향을 주지 않고 새로운 필드와 유형 추가가 가능하다.
GraphQL API : FRESH
- Fast : 데이터를 얻기 위해 여러 번 호출하지 않고 특정 필드만 선택하여 쿼리를 제공한다,
- Single Request : 단일 요청으로 애플리케이션에 필요한 모든 데이터를 가져온다.
- Evolve API : 기존 쿼리에 중단없이 새로운 API 기능 추가가 가능하다.
- Strongly typed : 쿼리하기 전에 데이터에 대해 설명하여 강력하게 정의된 데이터 유형을 사용한다.
- Hierarchical Structure : 객체 간 관계가 그래픽 구조로 정의된 계층적 구조를 제공한다.
- Query Complexity : 한 번의 요청에 너무 많은 중첩 필드 데이터를 요청하는 경우 성능에 문제가 될 수 있다.
- Caching : 간단한 캐시 구현이 어렵다.
- Rate Limiting : REST API에서는 특정 시간에 특정 양의 요청만 허용하는 것이 가능하지만 GraphQL에서는 어렵다.
- Google에서 개발한 오픈소스 원격 프로시저 호출(Remote Procedure Calls) 시스템
- 서비스를 효율적으로 연결하고 분산 시스템을 구축하기 위한 프레임워크
- 고성능, HTTP/2 프로토콜 사용 → Binary Message 전송
- 서비스 계약 정의 → Protocol Buffers(Protobuf) 언어
- Protobuf : 프로그래밍 언어에 상관없이 서비스 간 통신에 사용할 인터페이스 정의
- 여러 언어에서 사용가능한 크로스플랫폼 클라이언트 및 서버 바인딩 생성
- 프로그래밍 언어에 독립적, 서로 통신할 수 있는 서비스를 생성
- Protobuf 계약 정의 : 각 서비스에서 정의된 계약을 사용하여 통신 인프라 설정 코드 자동 생성
- gRPC를 사용하여 클라이언트 App에서 다른 서버 App의 메서드 호출이 가능하다.
장점
- SSL/TLS : 다양한 인증 방법 지원
- 멀티 플랫폼 : 다양한 언어 및 플랫폼 지원
- Binary Serialization : 이진 직렬화를 사용하여 JSON보다 높은 성능
- High Performance : HTTP/2 사용 시 30~40% 성능 향상 유도
- Bi-directional Streaming : 양방향 스트리밍 작업 지원
- 마이크로서비스 간 통신을 위해 백엔드 작업에서 주로 사용
- Low Latency + High Throughput
- P2P Real Time Comminucation
- gRPC Binary Message < JSON Text Message
- 실시간 웹 애플리케이션 개발 - 클라이언트에 지속적인 데이터 표시, 연결된 소켓으로 PUSH
- 채팅 애플리케이션 개발 - 구독자, 브로드캐스팅 채널 간의 메시지 교환, 메시지 송수신 처리 시 처리가 쉽고 빠르다.
| REST APIs | WebSocket APIs |
|---|---|
| 단방향/Stateless | 양방향/Stateful |
| 비용이 크다 | 비용이 적다 |
| CRUD 작업 처리 → 고수준 프로토콜 | 소켓 + 포트 사용 → 저수준 프로토콜 |
| 애플리케이션에 대한 요청 트래픽이 많은 경우 유리 | 실시간 애플리케이션 개발 |
| 요청이 발생할 때마다 TCP 연결이 필요 | 클라이언트와 서버 간 데이터 통신 전체에 동일한 TCP 연결 필요 |
| 수직적으로 연결 확장 가능 | 수평적으로 연결 처리 |
| 모든 요청에 오버헤드가 발생 | 오버헤드 없음 |
@GrpcClient("order-service")
private OrderServiceGrpc.OrderServiceBlockingStub orderStub; // ①. order-service gRPC 서버를 호출하기 위한 stub 주입
// ②. user-service(클라이언트)가 order-service(서버)에 gRPC 요청을 보내 주문 목록을 받아오는 부분
OrderRequest request = OrderRequest.newBuilder()
.setUserId(userId)
.build();
OrderResponse response = orderStub.getOrders(request); // ← 핵심
// ③. order-service(서버)가 user-service(클라이언트)로부터 gRPC 요청을 받아 DB에서 주문 목록을 조회한 후 응답을 반환하는 부분
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {
@Override
public void getOrders(OrderRequest request, StreamObserver<OrderResponse> responseObserver) {
Iterable<OrderEntity> orders = orderRepository.findByUserId(request.getUserId()); // DB 조회
// ... 응답 구성
responseObserver.onNext(response); // 응답 전송
responseObserver.onCompleted(); // 스트림 종료
}
}
// order-service에서 GraphQL 스키마 & 쿼리 핸들러 정의
// schema.graphqls
type Query {
ordersByUser(userId: ID!): [Order] ← GraphQL 엔드포인트 정의
}
// OrderGraphQLController.java
@QueryMapping
public List<ResponseOrder> ordersByUser(@Argument String userId) {
// /graphql 로 들어오는 쿼리를 처리
}// UserServiceImpl.java - getUserByUserId() 메서드
// GraphQL 쿼리 작성
String graphQLQuery = """
query($uid: ID!) {
ordersByUser(userId: $uid) {
orderId, productId, qty, unitPrice
}
}""";
// HTTP POST로 order-service GraphQL 엔드포인트 호출
ResponseEntity<Map> response = restTemplate.postForEntity(
"http://localhost:8082/graphql",
jsonRequest,
Map.class
);