@JulienTopcu
@JulienTopcu
?🤪?
@JulienTopcu
Je veux rechercher les trains qui desservent ma destination
@JulienTopcu
Architectural Styles and the Design of Network-based Software Architectures - Roy Fielding
@JulienTopcu
© Paramount Pictures - Interstellar
@JulienTopcu
© Paramount Pictures - Interstellar
@JulienTopcu
@JulienTopcu
© Marvel Studio - "Doctor Strange"
🤯
@JulienTopcu
@JulienTopcu
© 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": "..."
}
Je réserve ce que j'ai sélectionné
Je re-réserve le même train à une date ultérieure
Perte de la notion de sélection
Confusion sémantique avec le cas du dessus
@JulienTopcu
BookSpaceTrains.fromTheSelectionOf(search)
rebookSpaceTrains.atANewDate(bookingId,newdate)
POST /rebookings
{
"searchId" : "...",
}
POST /bookings
{
"bookingId" : "...",
"newDate": "..."
}
@JulienTopcu
Re-réserver
Pseudo-fonctionnel
@JulienTopcu
@JulienTopcu
@JulienTopcu
POST /bookings/{id}/rebook
{
"newDate": "..."
}
booking {
"bookingId": "{newId}",
"date": "{newDate}"
}
GET /bookings/{newId}
@JulienTopcu
@JulienTopcu
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
@JulienTopcu
© Lucasfilms - "STAR WARS: Revenge of the Sith"
@JulienTopcu
@JulienTopcu
Search
|_ availableSpaceTrains
|_ selection
|_ selectedSpaceTrains
PATCH /searches/{id}/selection
@JulienTopcu
Je sélectionne un train et un tarif
@JulienTopcu
@JulienTopcu
@JulienTopcu
@JulienTopcu
© 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 */
}
@JulienTopcu
class Search { //Aggregate
/*attributes*/
/*no setters*/
Search(Selection selection, SpaceTrains spacetrains, ...){...}
Search selectSpaceTrainWithFare(UUID spaceTrainId, UUID fareId) {}
Boolean isSelectionComplete() {...}
}
@JulienTopcu
GET /searches/{id}
{
"id": "c956d89b-73fc-49ce-a911-f0058c0672f6",
"criteria": {/*...*/},
"spaceTrains": [/*...*/],
"selection": {/*...*/}
}
@JulienTopcu
© Lucasfilms - "STAR WARS: Attack of the Clones"
@JulienTopcu
@JulienTopcu
bookSpaceTrains.fromTheSelectionOf(search)
rebookSpaceTrains.atANewDate(bookingId,newdate)
POST /bookings
{
"searchId" : "...",
}
POST /bookings
{
"bookingId" : "...",
"newDate": "..."
}
On est obligé de lire le contenu du payload pour en déduire le cas d'utilisation
@JulienTopcu
@JulienTopcu
PATCH /searches/{id}/selection
[
{
"op": "add",
"path": "/selectedSpaceTrains/-",
"value": {"trainNumber" : "MOON421", "fare" : "FIRST"}
}
]
Complexité accidentelle d'adaptation
Désencapsulation
@JulienTopcu
POST /searches/{id}/spaceTrains/{number}/fares/{code}/select
Search /* Modèle du Domaine */
|_ availableSpaceTrains
|_ selection
|_ selectedSpaceTrains
@JulienTopcu
POST /searches/{id}/spaceTrains/{number}/fares/{code}/select
© Touchstone Pictures - "The Hitchhiker's Guide to the Galaxy"
@JulienTopcu
© Marvel Studio - "Guardians of the Galaxy Vol.1"
en terme de workflow...
@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*/
@JulienTopcu
@JulienTopcu
@JulienTopcu
if(searchType == "roundtrip" && outBoundIsSelected && inBoundIsSelected){
displayBookingButton()
}
@JulienTopcu
if(searchType == "roundtrip" && outBoundIsSelected && inBoundIsSelected){
displayBookingButton()
}
@JulienTopcu
© Paramount Pictures - "Star Trek: The Next Generation"
@JulienTopcu
© martinfowler.com - "Richardson Maturity Model"
@JulienTopcu
Hypermedia As The Engine of Application State
@JulienTopcu
@JulienTopcu
@JulienTopcu
❌
❌
❌
❌
✔️
✔️
✔️
✔️
@JulienTopcu
❌
❌
❌
❌
✔️
✔️
✔️
✔️
@JulienTopcu
Repository
@JulienTopcu
© Paramount Pictures - "Star Trek"
@JulienTopcu
© Kirsten Luce for The New York Times
@JulienTopcu
© Kirsten Luce for The New York Times
@JulienTopcu
© Kirsten Luce for The New York Times
@JulienTopcu
© Kirsten Luce for The New York Times
© Cottonbro - Pexels
@JulienTopcu
Je sélectionne un train et un tarif
@JulienTopcu
@JulienTopcu
@JulienTopcu
@JulienTopcu
BookSpaceTrains.fromTheSelectionOf(search)
rebookSpaceTrains.atANewDate(bookingId,newdate)
POST /bookings
{
"searchId" : "...",
}
POST /bookings
{
"bookingId" : "...",
"newDate": "..."
}
@JulienTopcu
🚅
@JulienTopcu
@JulienTopcu
@JulienTopcu
@JulienTopcu
@JulienTopcu
interface SearchForSpaceTrains {
Search satisfying(Criteria criteria)
}
POST /searches
@JulienTopcu
From the Earth to the Moon
@JulienTopcu
@JulienTopcu
POST /searches
GET /searches/{id}
@JulienTopcu
POST /searches
GET /searches/{id}
PATCH /searches/{id}/selection
GET /searches/{id}/selection
@JulienTopcu
POST /searches
GET /searches/{id}
PATCH /searches/{id}/selection
GET /searches/{id}/selection
POST /bookings
GET /bookings/{id}
@JulienTopcu
@JulienTopcu
@JulienTopcu
@JulienTopcu
BookSpaceTrains.fromTheSelectionOf(search)
rebookSpaceTrains.atANewDate(bookingId,newdate)
POST /rebookings
{
"searchId" : "...",
}
POST /bookings
{
"bookingId" : "...",
"newDate": "..."
}
@JulienTopcu
class Search {}
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
Booking toBooking(UUID searchId)
}
@JulienTopcu
class Search { //Aggregate
/*attributes*/
/*no setters*/
Search(Selection selection, SpaceTrains spacetrains, ...){...}
Search selectSpaceTrainWithFare(UUID spaceTrainId, UUID fareId) {}
Boolean isSelectionComplete() {...}
}
interface SearchForSpaceTrains { //DomainService
Search satisfying(Criteria criteria)
}
interface BookSpaceTrains { //DomainService
Booking fromTheSelectionOf(Search thisSearch)
}
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
???
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
@JulienTopcu
interface ColumbiadExpressService {
Boolean addSpaceTrain(UUID searchId,UUID spaceTrainId,UUID fareId)
Boolean isSelectionComplete(UUID searchId)
}
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
interface ColumbiadExpressService {
Boolean addSpaceTrain(UUID searchId,UUID spaceTrainId,UUID fareId)
Boolean isSelectionComplete(UUID searchId)
}
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
interface ColumbiadExpressService {
Boolean addSpaceTrain(UUID searchId,UUID spaceTrainId,UUID fareId)
Boolean isSelectionComplete(UUID searchId)
}
???
@JulienTopcu
interface SearchManager {
Search createSearch(Criteria criteria)
void updateSearch(UUID searchId, Selection selection)
// Search <>-- SpaceTrains && Selection <>-- SpaceTrains
void updateSearch(UUID searchId, SpaceTrains spaceTrains)
Booking toBooking(UUID searchId)
}
interface ColumbiadExpressService {
Boolean addSpaceTrain(UUID searchId,UUID spaceTrainId,UUID fareId)
Boolean isSelectionComplete(UUID searchId)
}
@JulienTopcu
© Mickael Jackson - Thriller
@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 */
}
@JulienTopcu
class Search {
SpaceTrains getSpaceTrains(){}
SpaceTrains getSpaceTrains(Bound bound){}
Selection getSelection() {}
Boolean isSelectionComplete() {}
Search selectSpaceTrainWithFare(...) {}
Search selectSeatOptions(...) {}
}
GET /searches/{id}/spacetrains
GET /searches/{id}/spacetrains?bound={bound}
GET /searches/{id}/selection
GET /searches/{id}/selection/complete
@JulienTopcu
interface SearchForSpaceTrains {
infix fun satisfying(criteria: Criteria): Search // POST /searches
}
interface BookSomeSpaceTrains {
infix fun `from the selection of`(search: Search): Booking // POST /bookings
}
data class Search(/*...*/) {
fun selectSpaceTrainWithFare(/*...*/) : Search // PUT /searches/{id}/selection
fun selectSeatOptions(/*...*/) : Search // PUT /searches/{id}/selection
fun isSelectionComplete() : Boolean // GET /searches/{id}/selection
}
@JulienTopcu
/*Search*/
{
"id": "c956d89b-73fc-49ce-a911-f0058c0672f6",
"criteria": {/*...*/},
"spaceTrains": [/*...*/],
"selection": {/*...*/}
}
@JulienTopcu
@JulienTopcu
"selection": {
"href": "http://localhost/searches/{id}/selection"
},
"create-booking": {
"href": "http://localhost/bookings?searchId={id}"
},
"all-outbounds": {
"href": "http://localhost/searches/{id}/spacetrains?journeyOrder=0&onlySelectable=false"
},
"outbounds-for-current-selection": {
"href": "http://localhost/searches/{id}/spacetrains?journeyOrder=0&onlySelectable=true"
},
@JulienTopcu
Bucket GraphQL
@JulienTopcu
@JulienTopcu
@JulienTopcu
"price" : {
"amount" : 1000,
"currency" : "FR"
}
@JulienTopcu
"price" : {
"amount" : 150,
"currency" : "EUR"
}
@JulienTopcu
@JulienTopcu
Complexité accidentelle d'adaptation => Adaptation de isSelectionComplete de Search (aggreggat necessitant criteria) vers un /searches/../selection et les select* alors qu'ils devraient être niveau Search
@JulienTopcu
"Charge cognitive" => on se positionne sur une ressource selection dans l'API alors que l'utilisateur clique sur une fare dans un spacetrain
@JulienTopcu
Perte d'encapsulation mise a jours des états
JSON + CRUD => API anémique
PUT ../selection pour SeatOptions & Fares
POST /searches et bookings qui peuvent être vide
@JulienTopcu
@JulienTopcu
=> Logique déportée dans le consommateur exemple quand est-ce qu'on est pret a booker
@JulienTopcu
As mentioned above, a resource can have many identifiers. In other words, there may exist two or more different URI that have equivalent semantics when used to access a server. It is also possible to have two URI that result in the same mechanism being used upon access to the server, and yet those URI identify two different resources because they don't mean the same thing.
Semantics are a by-product of the act of assigning resource identifiers and populating those resources with representations. 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. In other words, there are no resources on the server; just mechanisms that supply answers across an abstract interface defined by resources. It may seem odd, but this is the essence of what makes the Web work across so many different implementations.
@JulienTopcu
@JulienTopcu
interface SearchForSpaceTrains {
infix fun satisfying(criteria: Criteria): Search
}
interface BookSomeSpaceTrains {
infix fun `from the selection of`(search: Search): Booking
}
data class Search(/*...*/) {
fun getSpaceTrainWithNumber(wantedNumber: String) : SpaceTrain {}
fun selectSpaceTrainWithFare(spaceTrainNumber: String, fareId: UUID, resetSelection: Boolean) : Search {}
fun isSelectionComplete() : Boolean {}
fun selectableSpaceTrains(bound: Bound) : List<SpaceTrain> {}
}
@JulienTopcu
class Search {
fun create() : Search {}
fun read() : Search {}
fun update() : Search {}
fun delete() : Search {}
}
@JulienTopcu
© Peugeot - "Le modeleur"
@JulienTopcu
© The Simpsons - "Lisa's Pony"
@JulienTopcu
@JulienTopcu
@JulienTopcu
@JulienTopcu