Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate Scala.js client code from a Service API #571

Closed
ignasi35 opened this issue Mar 2, 2017 · 18 comments

Comments

@ignasi35
Copy link
Member

commented Mar 2, 2017

(from the ML)

Given a Service API there should be a tool to generate a Scala.js client to interact with that service.

@drdozer

This comment has been minimized.

Copy link

commented Mar 2, 2017

(copied from ml)

Thanks for the feedback. My immediate scenario is calling from the browser. I can imagine lots of other use-cases (like auto-generating npm modules to use in node.js), but it's dangerous to go too far down the road of imagining what someone else may want and building something based upon that. I guess what I need client-side is something like:

val fooClient = FooClient.forService(serviceMountpointUrl)

I'm assuming that the service is exposed via the service-gateway plumbing (there may be other valid choices for an npm module I suppose, if it is 'internal' to the services deployment).

I was assuming that only services with ACL would be exposed.

A related issue that I've hit is that the case classes representing the rest requests and responses are currently serialized by default using Play's json bindings, but these are not scala-js friendly. This means that if you make a scalajs crossbuild project for these request/response classes that you have jump through some hoops as the serializers can't live in the companion objects. If you went down the code-generation rout, this wouldn't be an issue, as you could just generate independent client case classes matching these request/response server-side classes. But it's an issue now.

@drdozer

This comment has been minimized.

Copy link

commented Mar 2, 2017

For example, see the autowire project for something that generates both client and server bindings for a service:

https://github.com/lihaoyi/autowire

Not suggesting that lagom should use autowire, or even do things in the same way, but it's proof of principle that this can be done.

@erip

This comment has been minimized.

Copy link
Contributor

commented Jul 26, 2018

As a part of this, I think some parts of the Java standard lib, Play, and Akka will also need to have a provided ScalaJS cross-compilable version.

I think a good list is...

Java

  • java.net.URLEncoder

Akka

  • akka.Done
  • akka.NotUsed
  • akka.util.ByteIterator
  • akka.util.ByteString

Play

  • play.api.Environment
  • play.api.Mode

Then there will need to be a good amount of Lagom which should hopefully be "straightforward" thereafter. I think this is a good start...

Lagom

  • com.lightbend.lagom.internal.scaladsl.api.broker.TopicFactory
  • com.lightbend.lagom.internal.scaladsl.client.ScaladslClientMacroImpl
  • com.lightbend.lagom.scaladsl.api.{ Descriptor, Service, ServiceCall, ServiceInfo, ServiceLocator}
  • com.lightbend.lagom.scaladsl.api.broker.{ Subscriber, Topic }
  • com.lightbend.lagom.scaladsl.api.deser.{ ExceptionSerializer, MessageSerializer, PathParamSerializer }
  • com.lightbend.lagom.scaladsl.api.transport.{ Exceptions, MessageProtocol, Method }
  • com.lightbend.lagom.scaladsl.client.{ ScaladslServiceClientInvoker, ServiceClient, ServiceLocators }
  • com.lightbend.lagom.scaladsl.persistence.{ AggregateEvent, AggregateEventTag }
@PerWiklander

This comment has been minimized.

Copy link

commented Sep 14, 2018

I found this since I am looking at calling my lagom services from ScalaJS.

  1. The above mentioned problem of defining JSON formats in case class companion objects is no longer a problem since play.api.libs.json works with ScalaJS now.

  2. My first idea, before reading this thread, was to make a macro that creates Autowire traits from Lagom services, but just code generating a client lib would of course work as well.

  3. There might be some value in having a service client in ScalaJS that looks just like the service client server side, but I don't know if this would make it harder than it needs to be.
    Personally I'd prefer having this trait in the client:

trait DataSetService {
  def createDataSet(newDataSet: NewDataSet): Future[DataSet]
}

Instead of seeing things like:

trait DataSetService {
  def createDataSet: ServiceCall[NewDataSet, DataSet]
}

.

But this is based on previous experience with Autowire and just using simple http requests from the browser.

Lagom could do more, like support streaming events over websocket, by just using a service endpoint that returns a stream.

@erip

This comment has been minimized.

Copy link
Contributor

commented Sep 14, 2018

I don't see why we couldn't split the wickets: the service API could present interfaces that are implemented for the server-side and client-side. The server side would make WSClient calls, the client side would be AJAX. This could easily preserve the same API, too.

And btw, @PerWiklander, those play.api classes I mentioned above aren't in play.api.libs.json. :-)

@PerWiklander

This comment has been minimized.

Copy link

commented Sep 14, 2018

I was commenting on drdozer's "A related issue that I've hit is that the case classes representing the rest requests and responses are currently serialized by default using Play's json bindings, but these are not scala-js friendly. This means that if you make a scalajs crossbuild project for these request/response classes that you have jump through some hoops as the serializers can't live in the companion objects."
This is no longer correct.

@dwijnand dwijnand added the backlog label Feb 14, 2019
@dwijnand dwijnand removed the status:backlog label May 9, 2019
@mliarakos

This comment has been minimized.

Copy link
Contributor

commented Jul 6, 2019

I developed a prototype Lagom ScalaJS client and an example project on how to use it.

Basically, I re-worked the Lagom service api projects to be ScalaJS cross-compilable. The major changes are:

  • added dependency on akka-js to provide Akka support in JS
  • added a compatibility project for Java and Play artifacts not available in JS
  • added a compatibility shim to support method reflection when building service clients
  • abstracted use of wsClient into a WebClient (similar to WebSocketClient) in order to provide a JS implementation
  • abstracted normalization of HTTP headers in order to provide a JS implementation
  • split apart some Lagom components into separate files in order to cross compile the basic ones needed to build a JS client

There is still work to be done to polish the code, but it provides a fully featured ScalaJS service client without having to modify a service api definition. Since it introduces a lot of changes to Lagom, I'm interested in feedback on this approach before I try to polish it.

@erip

This comment has been minimized.

Copy link
Contributor

commented Jul 8, 2019

As an aside, we've been using this internally (or some iteration of it) for awhile and haven't experienced anything particularly strange. 😄

@bagf

This comment has been minimized.

Copy link

commented Jul 10, 2019

Would it be feasible to use this Scala JS prototype to export a JS module? I'm ganna give it a try, thanks for sharing @mliarakos

@erip

This comment has been minimized.

Copy link
Contributor

commented Jul 19, 2019

@bagf If you compile with sbt fastOptJS you will get unminified JS clients for your lagom services. sbt fullOptJS will generate minified and potentially optimized clients. That said, this is developed so you can write your clients in Scala and get the JS for free.

@PerWiklander

This comment has been minimized.

Copy link

commented Aug 12, 2019

@mliarakos Is the fork taken from a particular lagom release, or just what happened to be in master at that time? I'd like to try this, but it would be nice to know what I'm running.

Do you have an idea of how complicated the patch is, i.e. would a lot of work be needed to rebase it on the upcoming 1.6 release, or is it mostly modifying files that are changed less often?

@erip

This comment has been minimized.

Copy link
Contributor

commented Aug 12, 2019

@mliarakos

This comment has been minimized.

Copy link
Contributor

commented Aug 12, 2019

@PerWiklander the fork is taken from master right before Akka was bumped to 2.6 RC. This is only because the Scala.js protoype uses Akka.js which doens't have a 2.6 release yet.

The patch isn't terribly complicated, but it does have a large impact. That is mostly because many sub-projects have to be reorganized to be cross compiled. There aren't that many files that have substantive changes.

However, @renatocaval mentioned on the contributors gitter that they are looking to reduce the size of the Lagom code base and would prefer a side project. So, as @erip mentioned, I've been working on an side project version called lagom.js. It's also still a work in progress, but I'd recommend trying that out instead. There's a branch of the example project that uses lagom.js if you wan't to test that out.

EDIT: The master branch of the example project is now the version that uses lagom.js. The old version is now in a separate branch.

@PerWiklander

This comment has been minimized.

Copy link

commented Aug 12, 2019

Oh, a separate lagom.js project looks much better. What (if anything) do you feel is lacking in features right now?

@mliarakos

This comment has been minimized.

Copy link
Contributor

commented Aug 13, 2019

The two main features lagom.js doesn't have are circuit breakers and the full range of service locators.

A service client generated by lagom.js does not support circuit breakers. In fact, I had to remove circuit breakers from that part of the code base. I've worked through attempting to implement them, but run into several issues that don't appear to be easily fixable at this point. So, all service calls are invoked without circuit breakers. However, configuring circuit breakers in a service definition is supported. This means you don't have to change your service definition, for example:

trait ExampleService extends Service {

  def greeting: ServiceCall[NotUsed, String]

  override def descriptor: Descriptor = {
    import Service._
    named("example")
      .withCalls(
        restCall(Method.GET, "/greeting", greeting)
          .withCircuitBreaker(CircuitBreaker.identifiedBy("greeting"))
      )
  }

}

The circuit breaker configuration is just ignored by the lagom.js service client.

Right now, the only available ServiceLocator implementation is StaticServiceLocator, which means you have to provide static locations for your services. I might be able to implement some of the others, but I expect some might not be feasible. The example application builds the service URI using the hard coded dev mode port. There are several other potential approaches:

  • use the the gateway URI
  • have the Play application determine the service URL and pass it into the JavaScript code that builds the service client
@PerWiklander

This comment has been minimized.

Copy link

commented Aug 13, 2019

@renatocaval

This comment has been minimized.

Copy link
Member

commented Aug 27, 2019

Folks, I will close that issue because there is now lagom.js project.

@erip / @mliarakos, when ready we can add link the project on the lagom samples. Just let us know (or send a PR :-) )

@mliarakos

This comment has been minimized.

Copy link
Contributor

commented Aug 28, 2019

I'm working out a few final things before I publish an actual release. I'll submit a lagom samples PR after that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.