root
which can pull out that request ID, configure the logger and return the logger. I call it withRequestLogger
post("foo" :: withRequestLogger) { logger: Logger =>
@hderms you just hit the most actual topic for me, so I have tons of answers :)
If I understand correctly, you create a logger per request (due to the unique request context) and pass it around. If it's so, then I strongly advise using the famous Reader
monad!
You might ask why, and the answer is - because you can pass around logger, context, whatever you feel fancy to pass around without polluting your interfaces API.
The way we solve it in Zalando is to use tagless final + cats-mtl to extract context whenever you need it (actually, that's a topic for a blog post or even a tech talk). Nevertheless, you can fix on specific monad (Reader
) and go with it for a while.
Then, to compile Finch endpoints into Finagle service, you're required to have Effect
(or ConcurrentEffect
) for your monad. Reader doesn't have them out of the box, and the reason is simple: what should be the initial environment?
You have two options here:
mapK
over Endpoint.Compiled
and in natural transformation define the initial environment for your reader (say, NoopLogger?). Then in the next Kleisli
redefine it based on the request, instantiating the logger you need and using local
to propagate this environment down to the next element in the chain. Effect
for Reader[F[_] : Effect, YourEnv, *]
that will run this monad with some initial YourEnv
, so finch would pick it up to convert Compiled
to Service
Voilà, you don't have these logger endpoints everywhere, with loggers as parameters being passed here and there.
And it's not over yet! Just this night I've published the first version of Odin:
https://github.com/valskalla/odin
It's Fast & Functional logger that is not that features-fancy as log4j yet but has basic options available with something special on top. One of them: context being first-class citizen, so you don't need to mess around with ThreadLocal MDC and/or create loggers per context. It even has a contextual logger that can pick up your context from any ApplicativeAsk
(that is Reader) and embed it in the log for you.
It's not that I suggest you to pick it right away and use in production today, we are still to battle test in production this month, as well as some features might be missing. But you might be interested to subscribe to it and follow, and who knows if one day you can start using it in your project :)
*Local
things personally. It's even worse than implicits if you think about it. You should believe that someone somewhere put the required data into the magical box of *Local
before the moment you're going to use it
local
with a new tracing logger.
Hello everyone.
I faced one little problem using finch to build json REST API application.
In my application I have 2 entities User
and Pet
and they both have CRUD like operations.
Both API groups have their own encoders and decoders (I'm using circe).
class UserResources[F[_]](userRepo: UserRepo[F]) extends Endpoint.Module[F] with UserCodecs {
val create: Endpoint[F, User] = post("user" :: jsonBody[User]) { user: User => userRepo.save(user).map(Ok(_)) }
val get: Endpoint[F, User] = get("user" :: path[Long]) { userId: Long => userRepo.findById(userId).map(Ok(_)) }
...
val endpoints = (create :+: get)
}
class PetResources[F[_]](petRepo: PetRepo[F]) extends Endpoint.Module[F] with PetCodecs {
val create: Endpoint[F, User] = post("pet" :: jsonBody[Pet]) { pet: Pet => petRepo.save(user).map(Ok(_)) }
val get: Endpoint[F, User] = get("pet" :: path[Long]) { userId: Long => petRepo.findById(userId).map(Ok(_)) }
...
val endpoints = (create :+: get)
}
Then I use them in this fashion:
val allEndpoints = (new UserResources(repo).endpoints :+: new PetResources(repo2).endpoints)
val api = Bootstrap.serve[Application.Json](allEndpoints).toService
But this call require same instances of Encoder/Decoder
which are defined in *Codecs
trait for .toService
call to materialise the service. I understand why we should have instances in both situations.
Could you please suggest to me how I can better organise code in similar fashion, but without codecs instances duplicate?
Compiler picks it up from there on its own without any imports.
If you need to describe it for types outside of your application (like library types), you might as well to keep it inside a package object (or just an object) and import those implicits from there
Hi @Igosuki I experimented with it once, don't remember the way but it's possible for certain
But to clarify, what do you mean exaclty by upon Finagle server termination
? Because usually you shut it down all together with the whole application, and Http.serve
returns forever-running Twitter Future
libraryDependencies ++= Seq(
"com.github.finagle" %% "finchx-circe" % "0.31.0",
"com.github.finagle" %% "finchx-generic" % "0.31.0"
)
F
and dependency injection in class constructor
IO[Output[Something]]
into a Future
by calling unsafeToFuture
. Since I had already and IO[...], I suppose this call to unsafeToFuture
is not necessary, if not undesirable. val productsQuery: Endpoint[IO, ProductListResponse] =
post( "products" :: jsonBody[SimpleRequest] ) { req: SimpleRequest =>
val origin = "austria"
val products: IO[Output[ProductListResponse]] =
productCache
.flatMap { cache => cache.ref.get(origin) }
.map { r => Ok(ProductListResponse(ctx.api.version, None, r.get.contents)) }
products.unsafeToFuture
} handle {
case e: IllegalArgumentException => handleProductList.BadRequest(e)
case e: Exception => handleProductList.InternalServerError(e)
}
Future
is required.[error] /home/rgomes/workspace/guided-repair-api/service/src/main/scala/Endpoints.scala:143:17: type mismatch;
[error] found : cats.effect.IO[io.finch.Output[api.model.ProductListResponse]]
[error] required: scala.concurrent.Future[?]
[error] products //XXX .unsafeToFuture //FIXME: investigate if there's a way to avoid this call
// These are magic imports which must survive IntelliJ attempts to "help us".
// @formatter: off
import cats.effect._
import cats.implicits._
import cats.syntax.apply._
import io.circe.generic.auto._
import io.finch._
import io.finch.circe._
// @formatter: on
MyApp
object into the Request
when the request is authorized.auth
filter only blocks not authorized requests, but does not pass MyApp
to compiled(req)
.trait Filters extends Whiteboard with StrictLogging {
import io.finch._
import cats.effect.IO
import cats.implicits._
import com.twitter.finagle.http.Status
import com.twitter.finagle.http.Response
def authorized(authorization: Option[String]): IO[MyApp] = tokenValidation.authorized(authorization)
val auth: Endpoint.Compiled[IO] => Endpoint.Compiled[IO] =
compiled => {
Endpoint.Compiled[IO] { req =>
authorized(req.authorization)
.redeemWith(
_ => (Trace.empty -> Right(Response(Status.Unauthorized))).pure[IO],
//FIXME: should pass MyApp object into the request
// See examples 2 and 3 of: https://finagle.github.io/finch/cookbook.html#defining-custom-endpoints
myapp => IO(println(myapp)) *> compiled(req))
}
}
...
}
@frgomes quick and dirty way would be to use Request#ctx
: https://twitter.github.io/finatra/user-guide/http/filters.html#using-c-t-finagle-http-request-ctx or Context
from Finagle that is roughly the same as ThreadLocal (but actually request local)
IMO, proper "functional" way would be to use a different monad instead and have an auth that would have a similar signature:
val auth: Endpoint.Compiled[ReaderT[IO, MyApp, *]] => Endpoint.Compiled[IO]
The idea here is to have underlying endpoints with effect ReaderT[IO, MyApp, *]
that runs inside of Endpoint.Compiled[IO]
where you have an access to request and can build the instance of MyApp
ReaderT
as your effect monad allows you to access environment MyApp
whenever you feel like it