• Stars
    star
    90
  • Rank 369,088 (Top 8 %)
  • Language
    Scala
  • License
    Apache License 2.0
  • Created over 9 years ago
  • Updated about 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Implement fast, type-safe HTTP webservices for Finagle

Fintrospect                       

Fintrospect is a Scala web-framework with an intelligent HTTP routing layer, based on the Finagle RPC framework from Twitter. Via a shared contract, it provides a simple way to implement fast webservice endpoints and HTTP clients which are:

  • Type-safe : auto-marshals request parameters/bodies into the correct primitive and custom types.
  • Auto-validating : enforce the correctness of required/optional request parameters/bodies, generating a BadRequest if the contract is broken.
  • Auto-documenting : runtime generation of endpoint documentation such as Swagger JSON or web sitemap XML. Generates JSON Schema for example object formats to be included in these API docs.
  • Uniform : reuse the same contract to define both server endpoints and HTTP clients This also allows extremely low effort fake servers to be created

Additionally, Fintrospect provides a number of mechanisms to leverage these routes:

  • Easily build type-safe HTTP responses with a set of custom builders for a wide variety of message formats:
    • JSON: Argo, Argonaut, Circe, GSON, Jackson, Json4S, Play JSON, Spray JSON
      • Auto-marshaling of case classes instances to/from JSON (for Argonaut/Circe/Json4S/Play).
      • Implement simple PATCH/PUT endpoints of case class instances (Circe only).
    • Native implementations of XML, Plain Text, HTML, XHTML
    • MsgPack binary format
  • Serve static content from the classpath or a directory
  • Template View support (with Hot-Reloading) for building responses with Mustache or Handlebars
  • Anonymising headers for dynamic-path based endpoints, removing all dynamic path elements. This allows, for example, calls to particular endpoints to be grouped for metric purposes. e.g. /search/author/rowling becomes /search/author/{name}
  • Interacts seamlessly with other Finagle based libraries, such as Finagle OAuth2
  • Utilities to help you unit-test endpoint services and write HTTP contract tests for remote dependencies

Get it

Fintrospect is intentionally dependency-lite by design - other than Finagle, the core library itself only has a single non org.scala dependency. No dependency on Scalaz, Cats or Shapeless, so there are no compatibility headaches.

To activate the extension library features (JSON, templates etc), additional dependencies are required - please see here for details.

Add the following lines to build.sbt - the lib is hosted in Maven Central and JCenter:

resolvers += "JCenter" at "https://jcenter.bintray.com"
libraryDependencies += "io.fintrospect" %% "fintrospect-core" % "17.0.0"

See the code

See the examples or cookbook in this repo, or clone the full example application repo.

Learn it

See the full user guide here, or read on for the tldr; example. :)

Server-side contracts

Adding Fintrospect routes to a Finagle HTTP server is simple. For this example, we'll imagine a Library application (see the example above for the full code) which will be rendering Swagger v2 documentation.

Define the endpoint

This example is quite contrived (and almost all the code is optional) but shows the kind of thing that can be done. Note the use of the example response object, which will be broken down to provide the JSON model for the Swagger documentation.

// Response building methods and implicit conversion from ResponseBuilder -> Future[Response] pulled in here
import io.fintrospect.formats.Argo.ResponseBuilder._
import io.fintrospect.formats.Argo.JsonFormat.array

class BookSearch(books: Books) {
  private val maxPages = Query.optional.int("maxPages", "max number of pages in book")
  private val minPages = FormField.optional.int("minPages", "min number of pages in book")
  private val titleTerm = FormField.required.string("term", "the part of the title to look for")
  private val form = Body.form(minPages, titleTerm)

  private def search() = Service.mk[Request, Response] { 
    request => {
      val requestForm = form <-- request
      Ok(array(
        books.search(
            (minPages <-- requestForm).getOrElse(MIN_VALUE), 
            (maxPages <-- request).getOrElse(MAX_VALUE),
            titleTerm <-- requestForm
        ).map(_.toJson)))
    }
  }

  val route = RouteSpec("search for books")
    .taking(maxPages)
    .body(form)
    .returning(Status.Ok -> "we found your book", array(Book("a book", "authorName", 99).toJson))
    .returning(Status.BadRequest -> "invalid request")
    .producing(ContentTypes.APPLICATION_JSON)
    .at(Method.Post) / "search" bindTo search
}

Define a module to live at http://{host}:8080/library

This module will have a single endpoint search:

val apiInfo = ApiInfo("Library Example", "1.0", Option("Simple description"))
val renderer = Swagger2dot0Json(apiInfo) 
val libraryModule = RouteModule(Root / "library", renderer)
    .withRoute(new BookSearch(new BookRepo()).route)
Http.serve(":8080", new HttpFilter(Cors.UnsafePermissivePolicy).andThen(libraryModule.toService)) 

View the generated documentation

The auto-generated documentation lives at the root of the module, so point the Swagger UI at http://{host}:8080/library to see it.

Client-side contracts

Declare the fields to be sent to the client service and then bind them to a remote service. This produces a simple function, which can then be called with the bindings for each parameter.

Since we can re-use the routes between client and server, we can easily create fake implementations of remote systems without having to redefine the contract. This means that marshalling of objects and values into/out of the HTTP messages can be reused.

  val theDate = Path.localDate("date")
  val gender = FormField.optional.string("gender")
  val body = Body.form(gender)

  val sharedRouteSpec = RouteSpec()
    .body(body)
    .at(Get) / "firstSection" / theDate

  val fakeServerRoute = sharedRouteSpec bindTo (dateFromPath => Service.mk[Request, Response] {
    request: Request => {
      // insert stub server implementation in here
      println("Form sent was " + (body <-- request))
      Ok(dateFromPath.toString)
    }
  })

  Await.result(new TestHttpServer(10000, fakeServerRoute).start())

  val client = sharedRouteSpec bindToClient Http.newService("localhost:10000")

  val theCall = client(
    body --> Form(gender --> "male"), 
    theDate --> LocalDate.of(2015, 1, 1)
  )

  println(Await.result(theCall))

Upgrading?

See the changelog.

Contributing

There are many ways in which you can contribute to the development of the library:

  • Give us a ⭐️ on Github - you know you want to ;)
  • Questions can be directed towards the Gitter channel, or on Twitter @fintrospectdev
  • For issues, please describe giving as much detail as you can - including version and steps to recreate

See the contributor guide for details.

More Repositories

1

refactoring-golf

A Refactoring Golf exercise
Java
53
star
2

protokruft

Generate a DSL to hide all the hideous GRPC boilerplate
Kotlin
20
star
3

databob.kotlin

Randomised, zero-boilerplate object builders
Kotlin
14
star
4

configur8

Nano-library which provides the ability to define typesafe (!) configuration templates for applications.
Scala
12
star
5

zeit

Clock and task scheduler for node.js applications, providing extensive control of time and callback scheduling in prod and test code
JavaScript
12
star
6

databob.scala

Randomised, zero-boilerplate object builders
Scala
9
star
7

hamsandwich

This micro-library provides Java extensions to the Hamcrest library to provide a convienient way of declaring and combining entity Matchers which can be used in either test or production code.
Java
7
star
8

databob

Random object generation for tests
JavaScript
6
star
9

balderdash

Builders for monitoring dashboards
Python
5
star
10

finagle-circuit

Http circuit-breaking for Finagle
Scala
5
star
11

springclean

Now is the Spring of our discontent. Made glorious Summer.
Java
4
star
12

http4k-demo-s3box

S3-backed Dropbox implemented in 100 lines http4k
Kotlin
4
star
13

dotfiles

Shell
3
star
14

rebackupable

Bulk export your Remarkable 2 content to your Mac in a single click
Kotlin
3
star
15

daviddenton

README
2
star
16

fintrospect-todo-backend

A simple fintrospect implementation for http://www.todobackend.com
Scala
2
star
17

fintrospect-example-app

An example application project for the Fintrospect library
JavaScript
2
star
18

q-ext

Q extensions
JavaScript
1
star
19

featherweight

Kotlin
1
star
20

lensparty

Kotlin
1
star
21

crossyfield

Pico-library for providing cross-field validation to Scala projects
Scala
1
star
22

finagle-dojo

Scala
1
star
23

niceassert

Improving the readability of test code
Java
1
star
24

metrique

Hierarchical metrics sent to StatsD.
Java
1
star
25

alpha-global

1
star
26

http4k-data-class-gen

Kotlin
1
star
27

http4k-k8s-api

Kotlin
1
star
28

http4k-blockchain

Kotlin
1
star