question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Make the HTTP feature more flexible to foster easy migrations

See original GitHub issue

As @fedefernandez @raulraja suggested, we should focus not only on greenfield projects and also on existing projects. So in spite of we considered that the HTTP endpoints didn’t need parameters (because could be inferred), we want to provide enough flexibility to let other projects be migrated into Mu without too many efforts.

As the first step, we should add these parameters to the @http annotation to let the derived server work as expected:

  • HTTP Method: GET, POST, PUT, etc.
  • Prefix
  • Operation

That along with the hostname and the port set when instantiating the server, would compose the entire endpoint paths.

METHOD http://host:port/prefix/operation

(where the prefix can be composed by several segments)

Issue Analytics

  • State:open
  • Created 5 years ago
  • Comments:24 (18 by maintainers)

github_iconTop GitHub Comments

4reactions
noelmarkhamcommented, May 23, 2019

I did some work to implement @fedefernandez’s suggestion for making the HTTP feature more flexible. I effectively hand-rolled what the macro would do (which really was an exercise for me to understand Mu and the HTTP feature a little better), and got it all working. I put what I did on the branch feature/583-more-http-verbs if you want to have a look, but be warned it’s in no shape for public consumption really!

Going down this road would lead to a very small and tidy macro implementation, but more work for the user to wire this all together.

A couple of points I had:

  1. In allowing the client to define their own routes, I had to make the F concrete so that it would work with the concrete .route[IO] call. I don’t think this is an issue on its own. It may be possible to keep the type constructor abstract, but I couldn’t see an immediate way to do this, but I didn’t spend much time on it.

  2. This felt more important than my previous point: By asking for implicits, by their nature, you can only ever have one type that matches, so if you have multiple routes that have the same signature, it’s not immediately clear that this can be done, or at least in a simple way. For example, if you have a service made with the following definition (I’m making this up but hopefully it’s realistic enough):

trait UserService[F[_]] {
   def getAllUsers(request: Empty.type): F[List[User]]
   def getActiveUsers(request: Empty.type): F[List[User]]
}

Then we’d expect two different pairs of implicits with the same type for these:

PartialFunction[Request[F], F[Empty.type]]
Kleisli[F, List[User], Response[F]]

Every logical parameterless ‘get’ (lowercase) is going have that same PartialFunction signature. These would need to be differentiated some way. Ideas such as

  • Change the interface
  • Have some kind of tagging

could work, but these don’t feel natural from a user’s point of view.

Similary, a RESTful style for other definitions throw up similar discussions:

trait UserService[F[_]] {
  def createUser(request: User): F[Empty.type]
  def deleteUser(request: User): F[Empty.type]
}

These fit very nicely into the REST model for POST/PUT and DELETE, but there’s no direct translaction due to the fact the implicits required would clash.

I’m still very new to this application, so perhaps I’ve missed something big, but would be interested to get some other thoughts on this. I do wonder if going down this road means hiding too much about the HTTP interface to be useful, and it seems like @rafaparadela’s suggested commit may get around some of these limitations.

4reactions
fedefernandezcommented, Apr 1, 2019

@rafaparadela instead of adding new configuration params or extra complexity to our http definitions I suggest re-using the http4s types. Let me explain it with an example.

Suppose you have the following service:

  final case class Request1(id: String)
  final case class Response1(access: Boolean)

  final case class Request2()
  final case class Response2()

  trait MyService[F[_]] {
    def op1(request: Request1): F[Response1]
    def op2(request: Request2): F[Response2]
  }

  // ...
  val myService: MyService[F] = ???

We could ask for the following implicits in the macro:

  val pf1: PartialFunction[Request[F], F[Request1]] = ???
  val pf2: PartialFunction[Request[F], F[Request2]] = ???

  val srv1: Kleisli[F, Response1, Response[F]] = ???
  val srv2: Kleisli[F, Response2, Response[F]] = ???

But providing some default values. For example, for the first one could be something like in the macro def:

  val pf1: PartialFunction[Request[F], F[Request1]] = {
    case msg @ POST -> Root / "op1" => msg.as[Request1]
  }

Then, the only thing we need to do is compose those implicits with the service function:

  val s1: PartialFunction[Request[F], F[Response[F]]] = pf1.andThen(_.flatMap(myService.op1)).andThen(_.flatMap(srv1.run))
  val s2: PartialFunction[Request[F], F[Response[F]]] = pf2.andThen(_.flatMap(myService.op2)).andThen(_.flatMap(srv2.run))

  HttpRoutes.of(s1 orElse s2)

This will allow the users to define their owns routes and their own responses, based on the types. For example, for the first case, suppose you want to have a GET operation, receiving the param in the path and returning forbidden when the field in the response is false. All you need to do is to provide the right implicits in the scope:

  implicit val pf1: PartialFunction[Request[F], F[Request1]] = {
    case GET -> Root / "op1" / myId => Applicative[F].pure(Request1(myId))
  }
  implicit val srv1: Kleisli[F, Response1, Response[F]] = Kleisli {
    case Response1(true)  => Ok()
    case Response1(false) => Forbidden()    
  }

Let me know what you think. @juanpedromoreno @raulraja thoughts?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Deployments: Generate Schema File and Content Migrations ...
I could be nice to be able to generate migrations for the changes made in the database from the app, I think a...
Read more >
Migration tool - Azure Database for PostgreSQL Flexible Server
The tool automates most of the migration steps to make the migration journey across Azure platforms as seamless as possible.
Read more >
Drupal 9 - Acquia
Acquia strives to make migrating, building, running, and extending your Drupal 9 application easy. Learn about our Drupal optimized products.
Read more >
Database Migrations: Modifying Existing Databases
Where databases are reasonably simple, this process of modifying existing databases is very simple too: in fact the process can be automated.
Read more >
A Simpler SharePoint Migration Tool - ShareGate
SharePoint migration made simple. ... Our Promote feature lets you turn nested subsites into top-level site collections in a couple of clicks. Bulk...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found