Google Group - http://groups.google.com/group/zoot-framework
http://search.maven.org/#search%7Cga%7C1%7Czoot
Server and client sample implementation:
Zoot uses Api traits to define the services 'contract':
trait SomeApi extends Api {
@endpoint(
method = RequestMethod.PUT,
path = "/simple")
def simpleMethod(someInt: Int): Future[Int]
}
Notes:
- It is possible to use primitive and non-primitive classes as parameters and return.
- Api methods must return Future, otherwise an exception will be thrown.
- Apis should always be traits, not classes or abstract classes.
- To aggregate Apis, just use common trait inheritance.
By default all parameters are required and 'BadRequest' is returned if there is a missing parameter.
To define parameters as optional, just use the Option type:
trait SomeApi extends Api {
@endpoint(
method = RequestMethod.PUT,
path = "/simple")
def simpleMethod(someInt: Int, optionalString: Option[String]): Future[Int]
}
In this example, the method will be invoked using 'None' for 'optionalString' if the parameter is missing.
It is possible to define default values for parameters:
trait SomeApi extends Api {
@endpoint(
method = RequestMethod.PUT,
path = "/simple")
def simpleMethod(someInt: Int = 11): Future[Int]
}
If the 'someInt' is not specified in the request parameters, the default value '11' is used.
Use parameterized paths to extract parameters from the path:
trait SomeApi extends Api {
@endpoint(
method = RequestMethod.GET,
path = "/users/:user/name")
def userName(user: Int): Future[String]
}
The request for '/users/111/name' will invoke 'userName' using '111' for the 'user' parameter
By default, the response body is the value returned by the method and the http status is '200 Ok'. If the method throws an exception, '500 Internal Server Error' is returned.
It is possible to modify this behavior:
- By throwing an ExceptionResponse during the method execution.
- By returning a Future[Response].
The Client object provides Api instances for remote services:
val dispatcher: Request => Future[Response[String]] = ???
val client: SomeApi = Client[SomeApi](dispatcher)
You need to provide a 'real' dispatcher instance. Zoot provides implementations for Spray and Finagle.
Once you have the client instance, use Api methods as common method invocations:
val future: Future[Int] = client.simpleMethod(11)
val otherFuture: Future[String] = client.userName(22)
Please refer to the Scala Documentation for more details about Futures.
The Server object allows to create a server using an Api instance.
class SomeService extends SomeApi {
def simpleMethod(someInt: Int) = Future.success(someInt + 1)
}
val server: Request => Future[Response[String]] = Server[SomeApi](new SomeService)
The server is a function that can be used with the Spray or Finagle bindings.
The request and response values are serialized using a StringMapper.
Zoot provides the JacksonStringMapper implementation for json.
Please refer to the Spray or Finagle documentation for more details on how to create servers and clients.
implicit val mirror = scala.reflect.runtime.currentMirror
implicit val mapper = new JacksonStringMapper
val dispatcher = SprayClient(host = "localhost", port = 8080)
val client: SomeApi = Client[SomeApi](dispatcher)
implicit val mirror = scala.reflect.runtime.currentMirror
implicit val mapper = new JacksonStringMapper
implicit val system = ActorSystem("SomeSystem")
implicit val timeout = Timeout(1000 millis)
val server = Server[SomeApi](new SomeService)
val sprayActor = system.actorOf(Props(new SprayServer(server)))
IO(Http) ! Http.Bind(sprayActor, interface = "localhost", port = 8080)
implicit val mirror = scala.reflect.runtime.currentMirror
implicit val mapper = new JacksonStringMapper
val builder = ClientBuilder()
.codec(RichHttp[Request](Http()))
.hosts(s"$host:$port")
.hostConnectionLimit(10)
.requestTimeout(1000 millis)
val dispatcher = FinagleClient(builder.build())
val client: SomeApi = Client[SomeApi](dispatcher)
implicit val mirror = scala.reflect.runtime.currentMirror
implicit val mapper = new JacksonStringMapper
val address = new InetSocketAddress(port)
val builder =
ServerBuilder()
.codec(RichHttp[Request](Http()))
.bindTo(address)
.keepAlive(true)
.name("SomeServer")
val server = Server[SomeApi](new SomeService)
val finagleServer = FinagleServer(server, builder.build)
It is possible to define filters for zoot clients and servers. Example:
val requestLogFilter =
new Filter {
override def apply(request: Request, next: Service) = {
log(s"request $request")
next(request)
}
Use the filters when creating a server or client:
val server = requestLogFilter andThen Server[SomeApi](new SomeService)
val client = Client[SomeApi](requestLogFilter andThen dispatcher)
The name is a reference to the Zoot character from The Muppets Show, inspired by the jazz saxophonist Zoot Sims.
https://www.youtube.com/watch?v=CgfZVNv6w2E
"Forgive me Roy Fielding wherever you are!"
This is the buzzword of the moment and zoot uses non-blocking asynchronous IO.
Probably not. :)
If the client and server are using scala and zoot, it is a big win to reuse the Api traits to communicate. If not, just write them by your own. Anyway you need to specify how to invoke services.
No, since it uses runtime proxy generation. It is possible to remove this limitation using macros. Please open an issue if you would like have Scala.js compatibility.