Slide 1

Slide 1 text

Beyond REST – An Overview about Modern Web API Technologies Spring IO 2022 - Barcelona

Slide 2

Slide 2 text

May I introduce myself? ▪ Lars Dülfer … ▪ I am working for Novatec Consulting GmbH since 2016 ▪ Before, I was working as a software architect inhouse as well as for consultancies. ▪ I was working in Germany and abroad (South Africa) … ▪ ... in industries such as logistics, financial service, insurance etc. ▪ Software architecture is my professional passion

Slide 3

Slide 3 text

Warm Up – Who … ▪ … is providing a REST API? ▪ … is using Server-Sent Events? ▪ … is using Web Sockets?

Slide 4

Slide 4 text

NOTE In the following, the term REST API summarizes any level of HTTP / JSON based APIs except stated otherwise. 4

Slide 5

Slide 5 text

Sharing some experiences I made using and providing HTTP/REST APIs Explaining limitations of HTTP/REST APIs Introducing alternative technologies / protocols out there to develop APIs: GraphQL, gRPC and RSocket Explaining their capabilities compared to HTTP/REST APIs Showing examples how they can be implemented in Spring Talk about support in Spring Framework / Spring Boot No structured performance comparisons No deep dive into any of the alternative technologies What this talk is (not) about 5

Slide 6

Slide 6 text

▪ you had some fun ☺ and learned something new ▪ you got a first idea what GraphQL, gRPC and RSocket is about ▪ you can evaluate if any of them might help you with your API challenges at hand ▪ you got some inspiration what to investigate further So when you get out here later, I hope … 6

Slide 7

Slide 7 text

Photo by Daniel Lerman on Unsplash Why might you look for an alternative API technology? 7

Slide 8

Slide 8 text

▪ Widespread knowledge by developers ▪ Supported nearly everywhere (frameworks, languages, monitoring…) ▪ Security mechanisms well known and supported (OAuth etc.) ▪ Hypermedia Links (part of true REST APIs) can be used to transfer permission information / possible state transitions of resources ▪ JSON payloads are even easy to debug (as a human) ▪ HTTP Caching can be used ▪ HTTP/2 removes limits of concurrent connections by browsers and reduces TLS handshake costs by multiplexing ▪ Server-Send Events (SSE) allow server-streaming endpoints Without a doubt, REST APIs have many advantages, just to mention a few here 8

Slide 9

Slide 9 text

Let‘s assume the following scenario … 9

Slide 10

Slide 10 text

… that is built with the following services. 10

Slide 11

Slide 11 text

The corresponding REST endpoints for fetching data might look like this … 11 GET /projects GET /projects/{id} GET /projects/{id}/tasks GET /projects/{id}/participants GET /projects/tasks/{id} GET /projects/participants/{id} GET /companies GET /companies/{id} GET /companies/{id}/employees GET /companies/employees/{id} GET /users/current

Slide 12

Slide 12 text

The REST API endpoints for the task resource might look like this: 12 GET /projects/tasks/{id} POST /projects/tasks/{id} PUT /projects/tasks/{id} POST /projects/tasks/{id}/start POST /projects/tasks/{id}/complete POST /projects/tasks/{id}/reschedule DELETE /projects/tasks/{id}

Slide 13

Slide 13 text

You interact with all those endpoints in a request / response style 13

Slide 14

Slide 14 text

▪ You might have clients with very different needs to fetch data from a deeply nested, complex data structure ▪ You are often fetching large datasets and the payload size matters ▪ You have an application with high client-server communication and latency matters a lot ▪ You have an application that requires to stream data bi- directional between client and server. ▪ You need to receive sets of different streams (subscriptions) ▪ You need fire-and-forget semantics ▪ Your clients want to generate the client stubs So why would you consider looking for something else? 14

Slide 15

Slide 15 text

Our candidates for today are 15 ▪ Originally developed by Facebook ▪ Open-Sourced in 2015 ▪ Supported by the GraphQL Foundation ▪ Further information: graphql.org ▪ We are looking into the spring- graphql libary, a sub-project of spring ▪ Originally developed by Google as successor of their internal remote RPC framework. ▪ Open-sourced and released August 2016 ▪ Further information: grpc.io ▪ We are looking into the grpc- spring-boot-starter maintained by Michael Zhang (yidongnan) ▪ Originally developed by Netflix ▪ Based on reactive manifesto ▪ Further information: rsocket.io ▪ We are looking into spring- boot-starter-rsocket (which includes Spring RSocket support by Spring Messaging)

Slide 16

Slide 16 text

Photo by Pietro Jeng on Unsplash GraphQL – You will get what you‘ve asked for 16

Slide 17

Slide 17 text

Imagine you have to fetch a list of tasks with assigned participants information … 17

Slide 18

Slide 18 text

▪ You can build dedicated resources / endpoints to fetch data specific to your needs − requires to have control about the API like in a BFF approach ▪ You can use a custom expansion mechanism − https://my.domain/projects/task/4711?expand=participant − No standard way of doing this − Does usually not work on a field level, just on a “node“ level in a graph With a REST API, you have limited ways to prevent this 18 Let‘s see what GraphQL offers us …

Slide 19

Slide 19 text

GraphQL uses a single HTTP endpoint … 19

Slide 20

Slide 20 text

… or a websocket connection … 20

Slide 21

Slide 21 text

… that also allows GraphQL subscriptions. 21

Slide 22

Slide 22 text

A schema describes your GraphQL API … 22 type Query { projects: [Project] project(identifier: ID!): Project } type Project { identifier: ID! name: String tasks(from: Date, to: Date): [Task] participants: [Participant] … } type Task { name: String participant: [Participant] todos: [Todo] … } type Participant { name: String user: User company: Company … } …

Slide 23

Slide 23 text

… and the clients can now exactly fetch the data they need for their use case … 23 query { projects { name tasks (from: "2022-05-01") { name startDate } } } { "data": { "projects": [ { "name": "Project 5", "tasks": [] }, { "name": "Project 6", "tasks": [ { "name": "Task 293", "startDate": "2022-10-19" }, { "name": "Task 487", "startDate": "2022-04-17“ …

Slide 24

Slide 24 text

… making the server an executor of an arbitrary number of different queries … 24 @Controller class ProjectController(…) { @QueryMapping fun project( @Argument identifier: ProjectId ): CompletableFuture { … } @SchemaMapping fun tasks( project: Project, @Argument from: LocalDate?, @Argument to: LocalDate? ): CompletableFuture> { … } @BatchMapping fun participants( projects: List ): Mono>> { … } } type Query { project(identifier: ID!): Project projects: [Project] } type Project { identifier: ID! … tasks(from: Date, to: Date): [Task] participants: [Participant] } type Task { … } HTTP Request query { projects { name tasks (from: "2022-05-01") { name startDate } } }

Slide 25

Slide 25 text

Subscriptions allow to constantly send updates to a client based on their query… 25 type Subscription { projects: Project } type Project { … } type Task { … } @Controller class ProjectController( … ) { @SubscriptionMapping(“projects”) fun subscribeProjects( @AuthenticationPrincipal user: Principal ): Flux { … } @SchemaMapping fun tasks( project: Project, @Argument from: LocalDate?, @Argument to: LocalDate? ): CompletableFuture> { … } @BatchMapping fun participants( projects: List ): Mono>> { … } } websocket message wrapped in a graphql-ws frame: … subscription { projects { name tasks (from: "2022-05-01") { name startDate } } } …

Slide 26

Slide 26 text

Although called „query language“ GraphQL also supports changing data by „mutations“ 26 type Mutation { createProject( projectName: String!, plannedStartDate: Date!, deadline: Date!, companyId: String!): ID! } @Controller class ProjectController(…) { @MutationMapping fun createProject( @Argument projectName: String, @Argument plannedStartDate: LocalDate, @Argument deadline: LocalDate, @Argument companyId: CompanyId ): CompletableFuture = { … } } HTTP Request mutation { createProject( projectName: “Prepare Spring IO Talk", plannedStartDate: "2022-01-01", deadline: "2022-05-25", …. ) { identifier } }

Slide 27

Slide 27 text

There are many scenarios conceivable to add a GraphQL API to your existing application ... 27

Slide 28

Slide 28 text

Addresses the problem of overfetching or underfetching of data Reduces roundtrips between client and server Schema introspection simplifies exploring the API Nice for large datamodels that can be represented as graphs Fairly easy to add into an existing REST/HTTP-based backend - > it works over a single HTTP Endpoint (except subscriptions) Adds the „request-stream“ communication pattern via websockets Already well supported by Spring using spring-graphql Be careful about graph sizes (limits / paging required to not overload the server) Authorization might be trickey No use of default http-based caching No use of default http response codes for the actual query or mutation sent Summary / Takeaways GraphQL 28

Slide 29

Slide 29 text

Photo by Greg Rosenke on Unsplash gRPC – efficient, polyglott, with many comm. patterns 29

Slide 30

Slide 30 text

JSON payloads are not the most efficient way to package the stuff for transportation … 30 REST CALL: GET /projects/{id}/tasks (JSON Payload)

Slide 31

Slide 31 text

▪ write code representing the payload (request / response) ▪ configure a rest client or use an annotation-driven client ▪ or use a tool chain with, e.g., OPEN API Docs, Code generator based on that etc. Consuming a REST API requires either some manual work or a set of tools to get going … 31 Let‘s see what gRPC offers us …

Slide 32

Slide 32 text

gRPC uses HTTP/2 for transport … 32

Slide 33

Slide 33 text

… and allows various streaming scenarios, too. 33

Slide 34

Slide 34 text

Protobuf schemas describe your Services 34 … service TaskService { rpc createTask(CreateTaskRequest) returns (TaskIdentifier) {} rpc renameTask(RenameTaskRequest) returns (google.protobuf.Empty) {} rpc rescheduleTask(RescheduleTaskRequest) returns (google.protobuf.Empty) {} rpc startTask(TaskIdentifier) returns (google.protobuf.Empty) {} rpc completeTask(TaskIdentifier) returns (google.protobuf.Empty) {} rpc getTasksByProject(ProjectIdentifier) returns (TaskList) {} rpc subscribeTasksByProject(ProjectIdentifier) returns (stream Task) {} rpc getTaskById(TaskIdentifier) returns (Task) {} rpc subscribeTaskById(TaskIdentifier) returns (stream Task) {} } … … message CreateTaskRequest { ProjectIdentifier project_identifier = 1; string name = 2; optional string description = 3; google.protobuf.Timestamp start_date = 4; google.protobuf.Timestamp end_date = 5; } message ProjectIdentifier { string value = 1; } message TaskIdentifier { string value = 1; } …

Slide 35

Slide 35 text

The server implementation inherits from the generated services base class … 35 service TaskService { rpc createTask(CreateTaskRequest) returns (TaskIdentifier) {} rpc renameTask(RenameTaskRequest) returns (google.protobuf.Empty) {} rpc rescheduleTask(RescheduleTaskRequest) returns (google.protobuf.Empty) {} rpc startTask(TaskIdentifier) returns (google.protobuf.Empty) {} rpc completeTask(TaskIdentifier) returns (google.protobuf.Empty) {} rpc getTasksByProject(ProjectIdentifier) returns (TaskList) {} rpc subscribeTasksByProject(ProjectIdentifier) returns (stream Task) {} rpc getTaskById(TaskIdentifier) returns (Task) {} rpc subscribeTaskById(TaskIdentifier) returns (stream Task) {} } @GrpcService class TaskService(…) :TaskServiceGrpc.TaskServiceImplBase() { override fun createTask( request: CreateTaskRequest, responseObserver: StreamObserver ) { … } override fun startTask( request: TaskIdentifier, responseObserver: StreamObserver ) { … } override fun getTasksByProject( request: ProjectIdentifier, responseObserver: StreamObserver ) { … } override fun subscribeTasksByProject( request: ProjectIdentifier, responseObserver: StreamObserver ) { … } … }

Slide 36

Slide 36 text

Clients use the service stub to instantiate a gRPC client, either a blocking one … 36 class TaskServiceClientDemo(private val callCredentials: CallCredentials) { @GrpcClient("grpcapi") private lateinit var taskService: TaskServiceGrpc.TaskServiceBlockingStub fun doSomething(identifier: String): String { … taskService .withCallCredentials(callCredentials) .startTask(TaskIdentifier.newBuilder().setValue(identifier).build()) … } }

Slide 37

Slide 37 text

… or an asynchronous one, which is useful for unbound server-streaming, for example. 37 class TaskServiceClientDemo(private val callCredentials: CallCredentials) { @GrpcClient("grpcapi") private lateinit var taskService: TaskServiceGrpc.TaskServiceStub fun doSomething(…) { …. ProjectIdentifier.newBuilder().setValue(identifier).build().let { val observer: StreamObserver = object: StreamObserver { override fun onNext(value: Task) { … } override fun onError(t: Throwable?) { … } override fun onCompleted() { … } } taskService.subscribeTasksByProject(it, observer) } …. grpc: client: grpcapi: address: static://your.domain:8089 security: clientAuthEnabled: false trust-store: classpath:certs/server-truststore.jks trust-store-password: **** application.yml

Slide 38

Slide 38 text

gRPC is a great fit for service-to-service communication … 38

Slide 39

Slide 39 text

… or if Web Apps aren‘t your primary clients 39

Slide 40

Slide 40 text

Efficient packaging of data thanks to byte encoding using protobuf Supports most relevant communication patterns request-response | request-stream | stream-stream | stream-response Good fit for service-to-service communication, but also works with mobile clients Generated stubs make it easy for clients to call a remote service Schema introspection simplifies exploring the API Spring Cloud Gateway supports gRPC Routing You need to learn protobuf, live with it‘s limitations and understand versioning with it, too Not yet well supported by browsers: HTTP/2 supported but trailers aren‘t yet Summary / Takeaways gRPC 40

Slide 41

Slide 41 text

Photo by Christian M. on Unsplash RSocket – Duplex, reactive streams over the network 41

Slide 42

Slide 42 text

Using plain websockets feels a bit like having a highway but not a car yet to drive on it … 42 Let‘s see what RSocket offers us …

Slide 43

Slide 43 text

RSocket uses TCP or WebSocket for transport* 43 *Note: Further transports like Aeron possible but not discussed here today

Slide 44

Slide 44 text

Once a connection is established, both ends can become requester and responder 44

Slide 45

Slide 45 text

Beside the regular request-response flow, RSocket supports real fire-n-forget semantics 45

Slide 46

Slide 46 text

The „R“ in RSocket: Streams allow flow control on a per stream basis (not TCP flow control) 46

Slide 47

Slide 47 text

As with HTTP/2, Streams are multiplexed over the single connection 47

Slide 48

Slide 48 text

Spring supports annotated handlers to implement responders (Spring Messaging) 48 @Controller class ChatController { @MessageMapping("projects.{id}.chat") fun subscribeMessages( @DestinationVariable id: ProjectId, @AuthenticationPrincipal user: RegisteredUserProfile ): Flux { … } @MessageMapping("projects.{id}.chat.send") fun sendMessage( @DestinationVariable id: ProjectId, @AuthenticationPrincipal user: RegisteredUserProfile, message: String ) { … } … request-stream fire-and-forget

Slide 49

Slide 49 text

RSocketRequester provides a builder to create a client requester … 49 @Configuration class RSocketRequesterConfiguration(private val properties: CustomRSocketProperties) { …. @Bean fun rsocketRequester(rsocketRequesterBuilder: RSocketRequester.Builder) = rsocketRequesterBuilder.setupMetadata(token, authenticationMimeType).websocket(properties.uri) } ….

Slide 50

Slide 50 text

… that in turn can be used to send requests 50 @Service class ChatService(private val rsocketRequester: RSocketRequester) { fun subscribeMessages(projectIdentifier: String): Flux = rsocketRequester .route("projects.{id}.chat", projectIdentifier) .retrieveFlux(ChatMessage::class.java) fun sendMessage(projectIdentifier: String, message: String) { rsocketRequester .route("projects.{id}.chat.send", projectIdentifier) .data(message) .send() .block() } … } request-stream fire-and-forget

Slide 51

Slide 51 text

RSocket works well for web and mobile apps 51

Slide 52

Slide 52 text

Transport over WebSocket or TCP (others are possible, too, but not that relevant) Similar communication patterns as gRPC Real fire-and-forget pattern with no response / ack sent Streams have built-in application-level flow control handling backpressure Once connection is established, both ends can act as a requester (not possible in gRPC) Good fit for low-latency and high-frequency communications No schema / No code generation required Community does not seem to be very active since a while RSocket Java -> Last Release mid 2021 -> Maintenance? RSocket Broker idea not part of Spring anymore Summary / Takeaways RSocket 52

Slide 53

Slide 53 text

Photo by Kenny Eliason on Unsplash So many choices, and now? 53

Slide 54

Slide 54 text

There is nothing wrong with REST! For many use cases it will still be the best choice. BUT: There might be other uses cases and it‘s good to know your alternatives ☺ 54

Slide 55

Slide 55 text

What google trends tells us 55 GraphQL gRPC RSocket https://trends.google.com/trends/explore?q=rsocket,graphql,grpc Average

Slide 56

Slide 56 text

request-reponse request-stream request-response request-stream stream-response stream-stream fire-and-forget request-response request-stream stream-stream Only client can send requests Both ends can send requests HTTP or Websocket (or RSocket lately) HTTP/2 (limited usage for browser-based clients) TCP or WebSocket (and others like Aeron) app level flow control only on transport level schema-based / no code generation / text-based payload-format agnostic, can be JSON or CBOR, for example schema-based / code generation / protobuf only on transport level

Slide 57

Slide 57 text

A few guiding questions that could make you think about any of the presented alternatives 57 You build a data centric application or have various client needs for fetching graph-like data? Want to enhance an existing REST API? You like schemas and code generation for stubs, require streaming semantics or care about efficient transport on the wire? Web Apps are not your primary clients? You have highly interactive clients (web apps) and are looking for a flexible protocol to use with websockets? App level flow control is useful for you?

Slide 58

Slide 58 text

All examples can be found at: https://github.com/NovatecConsulting/YATT 58 Need help with the example? https://www.linkedin.com/in/larsduelfer/

Slide 59

Slide 59 text

59 Thanks for listening Enjoy Spring IO 2022 Barcelona ☺

Slide 60

Slide 60 text

Novatec Consulting GmbH Bertha-Benz-Platz 1 D-70771 Leinfelden-Echterdingen T. +49 711 22040-700 [email protected] www.novatec-gmbh.de 60 Distinguished Software Engineer / Project Lead Lars Duelfer Mobil: 0151 12167551 E-Mail: [email protected]