핵심 요약
마이크로서비스 RPC의 사실상 표준은 gRPC. 그러나 브라우저에서 직접 호출하려면 envoy proxy 같은 추가 레이어 필요. Connect는 이 한계를 해결한 신규 프로토콜 — gRPC 호환 + 브라우저 직접 호출.
- gRPC: HTTP/2 + Protocol Buffers, 가장 많은 언어 지원
- Connect: gRPC + REST + 브라우저 호환을 한 프로토콜로
- 둘 다 .proto 파일이 single source of truth
1. .proto 파일 — 공통 기반
// user.proto
syntax = "proto3";
package user.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc StreamEvents(StreamEventsRequest) returns (stream Event);
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
string id = 1;
}2. gRPC 서버 — Rust tonic
use tonic::{transport::Server, Request, Response, Status};
use user::user_service_server::{UserService, UserServiceServer};
use user::{GetUserRequest, User};
pub mod user { tonic::include_proto!("user.v1"); }
pub struct UserSvc;
#[tonic::async_trait]
impl UserService for UserSvc {
async fn get_user(&self, req: Request<GetUserRequest>) -> Result<Response<User>, Status> {
let id = req.into_inner().id;
Ok(Response::new(User {
id,
name: "Alice".into(),
email: "alice@example.com".into(),
}))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Server::builder()
.add_service(UserServiceServer::new(UserSvc))
.serve("[::1]:50051".parse()?)
.await?;
Ok(())
}3. gRPC 클라이언트 — Go
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := userpb.NewUserServiceClient(conn)
user, err := client.GetUser(ctx, &userpb.GetUserRequest{Id: "abc"})4. Connect-Web — 브라우저 직접 호출
Connect는 .proto는 동일, 그러나 HTTP/1.1 또는 HTTP/2로 작동. 브라우저에서 fetch로 직접 호출.
// generated client
import { createPromiseClient } from "@connectrpc/connect"
import { createConnectTransport } from "@connectrpc/connect-web"
import { UserService } from "./gen/user_connect"
const transport = createConnectTransport({
baseUrl: "https://api.example.com",
})
const client = createPromiseClient(UserService, transport)
const user = await client.getUser({ id: "abc" })
// 타입 자동, 응답 도 자동envoy proxy 없이 브라우저 직접 호출. JSON 또는 protobuf 둘 다 가능.
5. Connect 서버 — Go
import (
"connectrpc.com/connect"
"net/http"
)
func (s *UserSvc) GetUser(ctx context.Context, req *connect.Request[userpb.GetUserRequest]) (*connect.Response[userpb.User], error) {
return connect.NewResponse(&userpb.User{
Id: req.Msg.Id,
Name: "Alice",
}), nil
}
mux := http.NewServeMux()
mux.Handle(userv1connect.NewUserServiceHandler(&UserSvc{}))
http.ListenAndServe(":8080", h2c.NewHandler(mux, &http2.Server{}))6. 비교
| 항목 | gRPC | Connect |
|---|---|---|
| 전송 | HTTP/2 only | HTTP/1.1·HTTP/2 둘 다 |
| 브라우저 호환 | gRPC-Web 필요 + envoy proxy | 네이티브 |
| encoding | protobuf 기본 | protobuf·JSON 둘 다 |
| 스트리밍 | 풀 (양방향) | 서버 → 클라이언트 (단방향) |
| 언어 지원 | 13+ | 5 (Go·TS·Swift·Kotlin·Python) |
| 성능 | 최고 | 약간 낮음 (HTTP/1.1 시) |
7. 의사결정
gRPC 권장
- 마이크로서비스 간 통신 (서버 ↔ 서버)
- 대량 스트리밍 필요
- 최고 성능
- 다양한 언어 지원 필요
Connect 권장
- 브라우저 ↔ 서버 직접
- JSON·protobuf 둘 다 필요 (디버깅 용이)
- envoy 같은 proxy 없이 단순 배포
- 주로 Go·TypeScript 환경
8. 하이브리드 — 가장 흔한 패턴
// 단일 .proto에서:
// - 마이크로서비스 간: gRPC (tonic·grpc-go)
// - 브라우저: Connect (connect-web)
// - REST 호환 클라이언트: Connect의 JSON 모드
// 같은 서버가 Connect로 마운트되어 모두 처리9. 인증·관측성
gRPC 인터셉터
fn auth_interceptor(mut req: Request<()>) -> Result<Request<()>, Status> {
let token = req.metadata().get("authorization")
.ok_or(Status::unauthenticated("no token"))?;
// verify
Ok(req)
}Connect 미들웨어
const authInterceptor: Interceptor = (next) => async (req) => {
req.header.set("authorization", `Bearer ${token}`)
return next(req)
}10. OpenAPI vs gRPC vs Connect
| 표준 | 강점 | 약점 |
|---|---|---|
| REST + OpenAPI | 가장 광범위 호환 | 스키마 검증 약함 |
| GraphQL | over-fetching 방지 | 복잡함 |
| gRPC | 성능·스트리밍 | 브라우저 직접 호출 어려움 |
| Connect | gRPC + 브라우저 | 지원 언어 적음 |

댓글 0