Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Beyond REST - An Overview about Modern API Technologies

Beyond REST - An Overview about Modern API Technologies

REST is the most popular API technology used these days. It is well supported by programming languages, frameworks and all kind of monitoring tools as well as understood by many developers. It is therefore often the first choice when having to implement a web API for any kind of backend. But there are limitations and use cases where REST API’s do not fit that well. Luckily, there are a few interesting alternatives available these days. GraphQL is one such alternative so are gRPC and RSocket. In this talk I will introduce these technologies, explain their pros and cons compared to REST, talk about their maturity and how they are supported by Spring and Spring Boot.

Code examples can be found here: https://github.com/NovatecConsulting/YATT

Lars Duelfer

May 30, 2022
Tweet

Other Decks in Programming

Transcript

  1. 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
  2. Warm Up – Who … ▪ … is providing a

    REST API? ▪ … is using Server-Sent Events? ▪ … is using Web Sockets?
  3. NOTE In the following, the term REST API summarizes any

    level of HTTP / JSON based APIs except stated otherwise. 4
  4. 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
  5. ▪ 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
  6. Photo by Daniel Lerman on Unsplash Why might you look

    for an alternative API technology? 7
  7. ▪ 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
  8. 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
  9. 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}
  10. ▪ 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
  11. 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)
  12. Imagine you have to fetch a list of tasks with

    assigned participants information … 17
  13. ▪ 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 …
  14. 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 … } …
  15. … 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“ …
  16. … making the server an executor of an arbitrary number

    of different queries … 24 @Controller class ProjectController(…) { @QueryMapping fun project( @Argument identifier: ProjectId ): CompletableFuture<Project?> { … } @SchemaMapping fun tasks( project: Project, @Argument from: LocalDate?, @Argument to: LocalDate? ): CompletableFuture<List<Task>> { … } @BatchMapping fun participants( projects: List<Project> ): Mono<Map<Project, List<Participant>>> { … } } 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 } } }
  17. 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<Project> { … } @SchemaMapping fun tasks( project: Project, @Argument from: LocalDate?, @Argument to: LocalDate? ): CompletableFuture<List<Task>> { … } @BatchMapping fun participants( projects: List<Project> ): Mono<Map<Project, List<Participant>>> { … } } websocket message wrapped in a graphql-ws frame: … subscription { projects { name tasks (from: "2022-05-01") { name startDate } } } …
  18. 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<ProjectId> = { … } } HTTP Request mutation { createProject( projectName: “Prepare Spring IO Talk", plannedStartDate: "2022-01-01", deadline: "2022-05-25", …. ) { identifier } }
  19. There are many scenarios conceivable to add a GraphQL API

    to your existing application ... 27
  20. 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
  21. JSON payloads are not the most efficient way to package

    the stuff for transportation … 30 REST CALL: GET /projects/{id}/tasks (JSON Payload)
  22. ▪ 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 …
  23. 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; } …
  24. 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<TaskIdentifier> ) { … } override fun startTask( request: TaskIdentifier, responseObserver: StreamObserver<Empty> ) { … } override fun getTasksByProject( request: ProjectIdentifier, responseObserver: StreamObserver<TaskList> ) { … } override fun subscribeTasksByProject( request: ProjectIdentifier, responseObserver: StreamObserver<Task> ) { … } … }
  25. 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()) … } }
  26. … 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<Task> = object: StreamObserver<Task> { 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
  27. 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
  28. 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 …
  29. RSocket uses TCP or WebSocket for transport* 43 *Note: Further

    transports like Aeron possible but not discussed here today
  30. The „R“ in RSocket: Streams allow flow control on a

    per stream basis (not TCP flow control) 46
  31. 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<ChatMessage> { … } @MessageMapping("projects.{id}.chat.send") fun sendMessage( @DestinationVariable id: ProjectId, @AuthenticationPrincipal user: RegisteredUserProfile, message: String ) { … } … request-stream fire-and-forget
  32. 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) } ….
  33. … that in turn can be used to send requests

    50 @Service class ChatService(private val rsocketRequester: RSocketRequester) { fun subscribeMessages(projectIdentifier: String): Flux<ChatMessage> = 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
  34. 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
  35. 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
  36. 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
  37. 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?
  38. All examples can be found at: https://github.com/NovatecConsulting/YATT 58 Need help

    with the example? https://www.linkedin.com/in/larsduelfer/
  39. 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]