Phoenix Channel 을 활용한 프로젝트를 진행중이다.
Channel 의 메세지 타입을 FE 와 공유하고 BE 로직에서 강제하기 위한 방법들을 조사했다.
Protobuffer, Async API, Graphql Subscription 등 여러가지 선택지가 있었지만 Phoenix Channel 과 연동이 가능하고 FE / BE 모두 큰 공수없이 구현이 가능한 json schema 파일을 통한 type 관리를 도입하기로 했다.
예시 프로젝트와 함께 message type 강제를 위한 세팅을 해보자.
Phoenix 프로젝트 세팅
# Phoenix 프로젝트 세팅. view, db 는 필요없기 때문에 아래 옵션들 적용 mix phx.new wstyped --no-html --no-assets --no-ecto # 생성된 프로젝트 폴더로 이동 cd wstyped # User socket 생성 mix phx.gen.socket User # Room channel 생성 mix phx.gen.channel Room
위 명령어를 순차적으로 실행하면 Websocket 을 통한 간단한 message 전송을 지원하는 코드가 자동으로 생성된다. 코드들이 자동생성 되었으면 lib/wstyped_web/endpoint.ex 파일에 아래 코드를 추가한다.
# lib/wstyped_web/endpoint.ex socket "/socket", WstypedWeb.UserSocket, websocket: true, longpoll: false
Room channel 의 shout 이벤트를 처리하는 아래 함수의 메세지 타입을 정의해보자.
아래 함수의 payload 는 클라이언트에서 보낸 shout 이벤트의 message payload 이다.
# lib/wstyped_web/channels/room_channel.ex def handle_in("shout", payload, socket) do broadcast(socket, "shout", payload) {:noreply, socket} end
payload 가 특정 타입을 보장하도록 json schema 를 작성해보자.
payload 의 body 라는 key 로 들어오는 값은 string 이고 최소 1글자 최대 10글자로 설정했다.
// priv/schemas/shout_message_schema.json { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ShoutMessage", "description": "A shout message in the chat system", "type": "object", "properties": { "body": { "type": "string", "description": "The content of the shout message", "minLength": 1, "maxLength": 10 } }, "required": ["body"], "additionalProperties": false }
Message Validation
메세지를 broadcasting 하기 전에 schema 명세에 맞는지 확인하는 로직을 구현해보자.
먼저 json schema mix.exs 파일에 의존성 추가를 해준다.
# mix.exs defp deps do [ # ... {:ex_json_schema, "~> 0.10.2"} ] end
# 의존성 동기화 mix deps.get
lib/wstyped_web/channels/room_channel.ex 파일을 아래와 같이 수정하자.
defmodule WstypedWeb.RoomChannel do use WstypedWeb, :channel alias ExJsonSchema.Validator alias ExJsonSchema.Schema @schema "priv/schemas/shout_message_schema.json" # 추가 |> File.read!() |> Jason.decode!() |> Schema.resolve() # ... def handle_in("shout", payload, socket) do case validate_message(payload) do :ok -> broadcast(socket, "shout", payload) {:noreply, socket} {:error, errors} -> {:reply, {:error, %{errors: errors}}, socket} end end defp validate_message(payload) do case Validator.validate(@schema, payload) do :ok -> :ok {:error, errors} -> {:error, errors} end end end
이제 10글자가 넘는 메세지로 테스트를 해보면 아래와 같이 에러가 발생한다.
현재 코드는 모듈 attribute (@schema) 를 통해 스키마 명세를 하고있다. 모듈 attribute 는 컴파일 타임에 평가되는 상수라서 컴파일을 다시 하지 않으면 스키마 명세가 변경되지 않는다.
특별히 설정을 하지 않는 이상 priv 디렉토리 하위의 json 파일 변경으로 컴파일을 다시 하지는 않는다.
그래서 서버를 재시작 해도 반영이 되지 않기 때문에 스키마를 변경한 경우 아래와 같이 서버를 재시작하면 된다.
mix compile --force && mix phx.server
JSON schema 를 통해 메세지 타입을 강제하는 방법을 알아보았다.
json schema 이기 때문에 FE 에서 해당 파일을 참조해서 Typescript Type 으로 추출이 용이하다.
다만 single source of truth 를 위해서 BE 레포에 정의한 json schema 파일을 FE 에서 참조할 수 있어야 한다.
이에 대해서는 다음 포스팅에서 다루도록 하겠다.