This talk contains silver bullets that can make enthusiats of the cargo cult addicted

@JulienTopcu

@JulienTopcu

?🤪?

@JulienTopcu

I want to search for trains going to my destination

≠

I create a "Search" Resource

@JulienTopcu

Uniform Interface

Architectural Styles and the Design of Network-based Software Architectures - Roy Fielding

@JulienTopcu

Impedance mismatch

© Paramount Pictures - Interstellar

@JulienTopcu

© Paramount Pictures - Interstellar

Julien Topçu

Tech Coach

beyondxscratch.com

julien.topcu@owasp.org

craftsrecords.org

@JulienTopcu

@JulienTopcu

REST next level :
Crafting
Domain-Driven Designed web APIs

@JulienTopcu

PB#1 : False beliefs

© Lucasfilms - "STAR WARS: Revenge of the Sith"

@JulienTopcu

interface BookSpaceTrains {
    Booking fromTheSelectionOf(Search search)
}
POST /bookings

@JulienTopcu

interface RebookSpaceTrains {

    Booking atANewDate(UUID bookingId, LocalDate newdate)
}
interface BookSpaceTrains {
    Booking fromTheSelectionOf(Search search)
}
POST /bookings

@JulienTopcu

interface RebookSpaceTrains {

    Booking atANewDate(UUID bookingId, LocalDate newdate)
}
POST /bookings
interface BookSpaceTrains {
    Booking fromTheSelectionOf(Search search)
}
POST /bookings

@JulienTopcu

bookSpaceTrains.fromTheSelectionOf(search)
rebookSpaceTrains.atANewDate(bookingId,newdate)
POST /bookings
{
  "searchId" : "...",
}
POST /bookings
{
  "bookingId" : "...",
  "newDate": "..."
}

I book what I have selected

I'm rebooking the same train at a new date

Loss of the notion of Selection

Semantic confusion with the previous use case

@JulienTopcu

BookSpaceTrains.fromTheSelectionOf(search)
rebookSpaceTrains.atANewDate(bookingId,newdate)
POST /rebookings
{
  "searchId" : "...",
}
POST /bookings
{
  "bookingId" : "...",
  "newDate": "..."
}

@JulienTopcu

Re-booking

is an action, and not an entity

Pseudo-fonctionnel

@JulienTopcu

REST

must not lead

the design of your business domain !!!

@JulienTopcu

Pseudo-functional

Rebooking

Accidental Complexity

@JulienTopcu

POST /bookings/{id}/rebook
{
  "newDate": "..."
}
booking {
  "bookingId": "{newId}",
  "date": "{newDate}"
}
GET /bookings/{newId}

@JulienTopcu

Nouns VS Verbs

@JulienTopcu

Binding Semantics to URI

 At no time whatsoever do the server or client software need to know or understand the meaning of a URI -- they merely act as a conduit through which the creator of a resource (a human naming authority) can associate representations with the semantics identified by the URI

Architectural Styles and the Design of Network-based Software Architectures - Roy Fielding

@JulienTopcu

One of the secrets of the magic trick !

🧙🏿‍♂️

@JulienTopcu

PB#2 :
Confusing model and business

© Lucasfilms - "STAR WARS: Revenge of the Sith"

@JulienTopcu

The business 👩‍💼

@JulienTopcu

Search
|_ availableSpaceTrains
|_ selection
  |_ selectedSpaceTrains
PATCH /searches/{id}/selection

The model 📐

@JulienTopcu

I select a fare on a train

≠

I update my "Selection" sub-resource into my "Search" resource

@JulienTopcu

In the worst case

@JulienTopcu

In the best case

@JulienTopcu

Accidental Complexity of adaptation

Exposing our model to our consumer

↔

@JulienTopcu

PB#3 :
The encapsulation betrayal

© Lucasfilms - "STAR WARS: Revenge of the Sith"

@JulienTopcu

class Search {

    UUID id
    Criteria criteria
    SpaceTrains spaceTrains
    Selection selection

    UUID getId() {...}
    void setId(UUID id) {...}
    Criteria getCriteria() {...}
    void setCriteria(Criteria criteria) {...}

    /* only getters and setters */
}

Anemic domain

@JulienTopcu

data class Search { //Aggregate
    /*attributes*/
    /*no setters*/
   
    fun changeCriteria(criteria : Criteria) : Search {
    	return this.copy(criteria = criteria, spaceTrains = emptyList())
    }
}

Domain-Driven Design

We encapsulate the states
to expose behaviors

@JulienTopcu

GET /searches/{id}
{
        "id": "c956d89b-73fc-49ce-a911-f0058c0672f6",
        "criteria": {/*...*/},
        "spaceTrains": [/*...*/],
        "selection": {/*...*/}
}

REST

We de-encapsulate the behaviors

to expose the states

@JulienTopcu

© Lucasfilms - "STAR WARS: Attack of the Clones"

@JulienTopcu

C

R

U

D

ompletely

idiculous &

seless

DD

@JulienTopcu

bookSpaceTrains.fromTheSelectionOf(search)
rebookSpaceTrains.atANewDate(bookingId,newdate)
POST /bookings
{
  "searchId" : "...",
}
POST /bookings
{
  "bookingId" : "...",
  "newDate": "..."
}

We have to read the payload to deduce the use case

Loss of meaning through de-encapsulation

@JulienTopcu

@JulienTopcu

PATCH /searches/{id}/selection
[
  { 
    "op": "add", 
    "path": "/selectedSpaceTrains/-", 
    "value": {"trainNumber" : "MOON421", "fare" : "FIRST"}
  }
]

Accidental Complexity of adaptation

De-encapsulation

@JulienTopcu

POST /searches/{id}/spaceTrains/{number}/fares/{code}/select
Search /* Domain Model */
|_ availableSpaceTrains
|_ selection
  |_ selectedSpaceTrains

@JulienTopcu

POST /searches/{id}/spaceTrains/{number}/fares/{code}/select

How does the front-end know it can do this ???

© Touchstone Pictures - "The Hitchhiker's Guide to the Galaxy"

@JulienTopcu

PB#4 : Anemic API

© Marvel Studio - "Guardians of the Galaxy Vol.1"

regarding the workflow...

@JulienTopcu

@JulienTopcu

POST /searches   /*RoundTrip Criteria Submitted*/

GET  /searches/{id}/spaceTrains?bound=OUTBOUND

POST /searches/{id}/spaceTrains/{number}/fares/{code}/select

GET  /searches/{id}/spaceTrains?bound=INBOUND

POST /searches/{id}/spaceTrains/{number}/fares/{code}/select

POST /bookings   /*Trip Booked*/

How do we know that we must proceed in this order?

@JulienTopcu

What is preventing the front-end from:

  • Displaying the inbound trains before the outbound ones?

  • Trying the retrieve inbound trains for a one way trip?

  • Calling the booking creation endpoint although the selection is not yet complete?

@JulienTopcu

Nothing because our APIs are anemic

Only the front-end is implementing the business workflow !

↔

@JulienTopcu

if(searchType == "roundtrip" && outBoundIsSelected && inBoundIsSelected){
  displayBookingButton()
}

@JulienTopcu

if(searchType == "roundtrip" && outBoundIsSelected && inBoundIsSelected){
  displayBookingButton()
}

Some parts of the business logic are leaking in the consumer...

@JulienTopcu

We must encapsulate the workflow inside the API !

© Paramount Pictures - "Star Trek: The Next Generation"

@JulienTopcu

© martinfowler.com - "Richardson Maturity Model"

@JulienTopcu

HATEOAS

A linking system (URLs) allowing you to:

  • Expose the relationships between the domain objects

  • Discover the domain

  • Encapsulate the business workflow

Hypermedia As The Engine of Application State

@JulienTopcu

HATEOAS

@JulienTopcu

The links

The front-end must not try to understand how the URLs are formed !

⇒

A better decoupling
between the consumption
and the implementation of the domain model

@JulienTopcu

  • False beliefs on the REST nomenclature
  • Using verbs enrich the semantic of the API
     
  • Straight exposition of our model in the API
  • Business-oriented endpoint on the back-end
     
  • Exposing the states of the domain objects
  • Encapsulation of behaviors attached to the responsible objects in the API
     
  • Lack of workflow definition (anemic API)
  • HATEOAS to drive workflow from the API

REST impedance mismatch ?

❌

❌

❌

❌

✔️

✔️

✔️

✔️

REST impedance mismatch ?

@JulienTopcu

  • False beliefs on the REST nomenclature
  • Using verbs enrich the semantic of the API
     
  • Straight exposition of our model in the API
  • Business-oriented endpoint on the back-end
     
  • Exposing the states of the domain objects
  • Encapsulation of behaviors attached to the responsible objects in the API
     
  • Lack of workflow definition (anemic API)
  • HATEOAS to drive workflow from the API

❌

❌

❌

❌

✔️

✔️

✔️

✔️

@JulienTopcu

Repository

@JulienTopcu

Thanks!🙏🏻

© Paramount Pictures - "Star Trek"