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"

REST next level : Crafting Domain-Driven Designed web APIs

By Julien Topçu

REST next level : Crafting Domain-Driven Designed web APIs

You have just coded your business logic by applying the principles of Domain-Driven Design! But when comes the time to write your API, you are facing a serious issue! All the intention and the expression of your domain go up in smoke to fit the blankness methods GET, POST, etc. Denatured by the REST layer, the business workflow is then deported on the consumer side to compensate for the limited vocabulary of this well-known CRUD protocol... During this talk, we will see how to bring the business intent back inside the REST API by finally being able to expose our domain services and agregates' methods. The business workflow will also be encapsulated in the REST API in order to have the power to guide our consumers through the workflow of our domain.

  • 3,216