http4s에서 어떻게 IO#unsafeRunSync
없이 JSON 응답을 반환할 수 있는지 문의가 들어와서 보충설명 드립니다:
슬라이드에서 잠깐 보여드렸던 것처럼 http4s 서버는 Request[F] => F[Response[F]]
형태의 순수 함수입니다. 미들웨어나 루트 합성을 위해 Kleisli
나 OptionT
같은 데이터 타입들이 추가로 쓰이기는 하지만 결국 Request에서 Response로 가는 순수 함수이고, 중간에 데이터베이스 연결 같은 이펙트가 발생할 수 있기 때문에 Request => Response
대신 Request => F[Response]
와 같은 형태를 띄고 있다고 생각하시면 돼요. 아래같은 모듈이라고 생각하셔도 되겠네요.
trait HttpServer[F[_]] {
def handleRequest(req: Request): F[Response]
}
이 DSL 레이어는 TCP 소켓같은 저수준 관심사와 분리되어 있기 때문에 Request 값을 직접 만들어서 넘겨줌으로써 서버를 실제로 띄우지 않고도 서버 로직을 테스트할 수 있습니다. https://http4s.org/v0.20/testing/
한편 임의의 데이터 타입 A
를 Response[F]
형태로 변환하는 데는 EntityEncoder[F, A]
가 필요합니다. 보여드린 예제에서는 http4s-circe 모듈이 제공하는 jsonEncoderOf
함수를 통해 io.circe.Encoder[A]
에서 org.http4s.EntityEncoder[F, A]
를 유도할 수 있었어요. https://http4s.org/v0.20/entity/
좀더 구체적으로 들어가면 org.http4s.impl.EntityResponseGenerator
헬퍼가 암시적인 EntityEncoder[F, A]
클래스를 사용해 A
, F[A]
등을 F[Response[F]]
로 바꿔주는 역할을 하고 trait Http4sDsl
에 포함된 trait Responses[F[_]]
가 각 상태 코드 객체에 이런 문법 확장을 입혀 줍니다.
요약하면 http4s 서버는 Request[F] => F[Response[F]]
의 순수 함수 형태이기 때문에 HTTP 응답이 이펙트 F[_]
안에만 들어있으면 되고, 실제로 이 것이 실행되는 시점은 서버를 마운트하는 등의 구체적 행동이 일어난 시점 이후로 미뤄진다고 보시면 되겠습니다.