타입스크립트를 이용한 풀스택 강좌 - RPC정의

44523·2024년 1월 25일

RPC란?

Remote Procedure Call(원격 프로시저 호출)의 약자로, 별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 *함수나 **프로시저를 실행할 수 있게 하는 프로세스 간 통신 기술을 말한다.

다시 말해, RPC를 이용하면 프로그래머는 함수 또는 프로시저가 실행 프로그램이 존재하는 로컬 위치에 있든, 원격 위치에 있든 상관없이 동일한 기능을 수행할 수 있음을 의미한다.

RPC의 목표

  • Client-Server 간의 커뮤니케이션에 필요한 상세정보는 최대한 감춘다. (=> 언어나 환경에 구애를 받지 않는다! )

  • Client와 Server는 각각 일반 메소드를 호출하는 것처럼 원격지의 프로시저를 호출할 수 있다.

Sever-Client 통신

  • 클라이언트가 서버로 요청
  • 서버가 요청을 처리하고 응답
const res = await fetch("http://example.com/api/v1/user");
const user = await res.json();
console.log("이름: ", user.name, "나이: ", user.age);
console.log(user.asdf + user.qwer);

특정 사이트에 http요청을 보내고, 받은 응답을 json으로 해석해서 자바스크립트 객체를 만들고 그 객체에서 네임과 에이지의 프로퍼티를 출력하고 있다.

유저는 서버에서 온 문자열을 제이슨으로 해석한거라 타입 추론이 전혀 안된다.

유저의 타입은 any이고 타입스크립트에서는 타입 추론을 포기한다는 뜻이다.
any타입은 타입스크립트의 보호를 전혀 받지 못한다.

const res = await fetch("http://example.com/api/v1/user");
const user = (await res.json()) as { name: string; age: number };
console.log("이름: ", user.name, "나이: ", user.age);
console.log(user.asdf + user.qwer);

해결책은 제이슨 파싱한 결과를 as키워드를 사용해서 유저를 any 대신에 name과 age를 가진 오브젝트 타입으로 바꾼다.

하지만 이 방식은 작성자를 너무 신뢰한다는 문제가 있는데,

(any) as (...) 의 문제점

  • 런타임에 진짜 그 타입에 맞는 값이 들어있을까?
  • 서버가 수정될 가능성
  • 사람의 실수로 다른 타입을 명시할 가능성
  • 모든 통신에 일일이 타입을 명시해야됨
  • 심지어 두번씩 (서버/클라이언트)
  • 유지보수 불편함

이 문제를 개선하기 위해 RPC를 정의하여 사용한다.

RPC 정의하기

  • 원격지(서버)의 기능을 함수처럼 호출해서 사용
  • 함수처럼 호출 = 명확한 입출력 타입 존재
  • ex) gRPC: Google, Twirp: TwitchTV

서버와 클라이언트가 사용할 공통 타입 정하기

  • user: 이름
  • Post: 작성자, 내용, 시간, 댓글
  • Comment: 작성자, 내용, 시간
  • 각각의 고유 id 필요

서버의 기능을 함수들로 가지고 있는 타입을 정의하는데, 이 타입의 이름은 큰 의미없다.

가독성과 재사용성을 위해 입출력 타입을 따로 만들자.

export interface User {
  id: number;
  name: string;
}

export interface Post {
  id: number;
  author: User;
  body: string;
  timestamp: number;
  comments: Comment[];
}

export interface Comment {
  id: number;
  author: User;
  body: string;
  timestamp: number;
}

export interface CreatePostRequest {
  body: string;
}
export interface CreatePostResponse {}

export interface CreateCommentRequest {
  postId: number;
  body: string;
}
export interface CreateCommentResponse {}

export interface ReadPostRequest {
  postId: number;
}
export interface ReadPostResponse {
  post: Post;
}

export interface ReadRandomPostRequest {}
export interface ReadRandomPostResponse {
  post: Post;
}

export interface UpdateProfileRequest {
  name: string;
}
export interface UpdateProfileResponse {}

export interface ReadProfileRequest {}
export interface ReadProfileResponse {
  user: User;
}

export interface Preview {
  id: number;
  body: string;
  timestamp: number;
}

export interface ReadPreviewRequest {}
export interface ReadPreviewResponse {
  posts: Preview[];
  comments: Preview[];
}

export interface IRpc {
  createPost: (req: CreatePostRequest) => CreatePostResponse;
  createComment: (req: CreateCommentRequest) => CreateCommentResponse;
  readPost: (req: ReadPostRequest) => ReadPostResponse;
  readRandomPost: (req: ReadRandomPostRequest) => ReadRandomPostResponse;
  readProfile: (req: ReadProfileRequest) => ReadProfileResponse;
  readPreview: (req: ReadPreviewRequest) => ReadPreviewResponse;
  updateProfile: (req: UpdateProfileRequest) => UpdateProfileResponse;
}

0개의 댓글