@lukaszchrusciel
Rozterki i
Rozterki i
decyzje.
decyzje.
Czego się nauczyliśmy projektując
Czego się nauczyliśmy projektując
API Syliusa
API Syliusa
@lukaszchrusciel
QUIZ
QUIZ
@lukaszchrusciel
Introduction
Introduction
@lukaszchrusciel
@lukaszchrusciel
@lukaszchrusciel
@lukaszchrusciel
@lukaszchrusciel
Started in 2020
Started in 2020
1.12 with API Platform 2.7
1.12 with API Platform 2.7
(since 31st of Oct)
1.13 stabilized with API
1.13 stabilized with API
Platform 3.0
Platform 3.0
@lukaszchrusciel
Decisions &
Decisions &
consequences
consequences
@lukaszchrusciel
Strategic design
Strategic design
@lukaszchrusciel
ADRs
ADRs
@lukaszchrusciel
QUIZ
QUIZ
@lukaszchrusciel
Architecture Decision
Architecture Decision
Records
Records
@lukaszchrusciel
Architecture Decision Records
Architecture Decision Records
[short title of solved problem and solution]
Status: [proposed | rejected | ... ]
Date: [YYYY-MM-DD]
Context and Problem Statement
Decision Drivers
[driver 1, e.g., a force, facing concern, …]
…
Considered Options
[option 1]
Good, because [argument a]
Bad, because [argument b]
…
Decision Outcome
Chosen option: "[option 1]", because [justification].
References
[Link type] [Link to ADR]
@lukaszchrusciel
Architecture Decision Records
Architecture Decision Records
[short title of solved problem and solution]
Status: [proposed | rejected | ... ]
Date: [YYYY-MM-DD]
Context and Problem Statement
Decision Drivers
[driver 1, e.g., a force, facing concern, …]
…
Considered Options
[option 1]
Good, because [argument a]
Bad, because [argument b]
…
Decision Outcome
Chosen option: "[option 1]", because [justification].
References
[Link type] [Link to ADR]
@lukaszchrusciel
Architecture Decision Records
Architecture Decision Records
[short title of solved problem and solution]
Status: [proposed | rejected | ... ]
Date: [YYYY-MM-DD]
Context and Problem Statement
Decision Drivers
[driver 1, e.g., a force, facing concern, …]
…
Considered Options
[option 1]
Good, because [argument a]
Bad, because [argument b]
…
Decision Outcome
Chosen option: "[option 1]", because [justification].
References
[Link type] [Link to ADR]
@lukaszchrusciel
Architecture Decision Records
Architecture Decision Records
[short title of solved problem and solution]
Status: [proposed | rejected | ... ]
Date: [YYYY-MM-DD]
Context and Problem Statement
Decision Drivers
[driver 1, e.g., a force, facing concern, …]
…
Considered Options
[option 1]
Good, because [argument a]
Bad, because [argument b]
…
Decision Outcome
Chosen option: "[option 1]", because [justification].
References
[Link type] [Link to ADR]
@lukaszchrusciel
Architecture Decision Records
Architecture Decision Records
[short title of solved problem and solution]
Status: [proposed | rejected | ... ]
Date: [YYYY-MM-DD]
Context and Problem Statement
Decision Drivers
[driver 1, e.g., a force, facing concern, …]
…
Considered Options
[option 1]
Good, because [argument a]
Bad, because [argument b]
…
Decision Outcome
Chosen option: "[option 1]", because [justification].
References
[Link type] [Link to ADR]
@lukaszchrusciel
Architecture Decision Records
Architecture Decision Records
[short title of solved problem and solution]
Status: [proposed | rejected | ... ]
Date: [YYYY-MM-DD]
Context and Problem Statement
Decision Drivers
[driver 1, e.g., a force, facing concern, …]
…
Considered Options
[option 1]
Good, because [argument a]
Bad, because [argument b]
…
Decision Outcome
Chosen option: "[option 1]", because [justification].
References
[Link type] [Link to ADR]
@lukaszchrusciel
Conclusion
Conclusion
Conclusion
Conclusion
Architecture
Architecture
Architecture
Architecture
Decision Records
Decision Records
Decision Records
Decision Records
FTW
FTW
FTW
FTW
[short title of solved problem and solution]
Status: [proposed | rejected | ... ]
Date: [YYYY-MM-DD]
Context and Problem Statement
Decision Drivers
[driver 1, e.g., a force, facing concern, …]
…
Considered Options
[option 1]
Good, because [argument a]
Bad, because [argument b]
…
Decision Outcome
Chosen option: "[option 1]", because
[justification].
References
[Link type] [Link to ADR]
@lukaszchrusciel
GraphQL vs REST
GraphQL vs REST
@lukaszchrusciel
2020
2020
GraphQL
GraphQL
@lukaszchrusciel
Is it still?
Is it still?
@lukaszchrusciel
@lukaszchrusciel
@lukaszchrusciel
Is it not?
Is it not?
@lukaszchrusciel
GraphQL
GraphQL
Solves over fetching
Solves over fetching
and under fetching
and under fetching
By design
@lukaszchrusciel
GraphQL
GraphQL
Sends everything with
Sends everything with
POST
POST
It is possible to do it with GET
@lukaszchrusciel
GraphQL
GraphQL
Typed, nice
Typed, nice
documentation
documentation
out of the box
@lukaszchrusciel
GraphQL
GraphQL
Gracefully deprecation
Gracefully deprecation
of queries
of queries
Which was not possible with default
REST
@lukaszchrusciel
GraphQL
GraphQL
Promise high
Promise high
performance
performance
due to reduction of queries
@lukaszchrusciel
REST
REST
May solve over
May solve over
fetching and under
fetching and under
fetching
fetching
With sparefields sets and/or Vulcain
@lukaszchrusciel
REST
REST
Takes advantage of 30
Takes advantage of 30
years of web cache
years of web cache
development
development
Fake data institute™
@lukaszchrusciel
REST
REST
Typed, nice
Typed, nice
documentation
documentation
With OpenAPI
@lukaszchrusciel
REST
REST
Gracefully deprecation
Gracefully deprecation
of queries
of queries
With OpenAPI and HTTP Headers
@lukaszchrusciel
Quiz
Quiz
@lukaszchrusciel
In 2022 (...) REST API usage in
production grew by 10% now at 70%
compared to GraphQL 5% increased
usage. 1
https://rapidapi.com/guides/2022-state-of-apis-what-developers-are-saying
1.
@lukaszchrusciel
Conclusion
Conclusion
REST is a better default
REST is a better default
@lukaszchrusciel
High level API
High level API
design
design
@lukaszchrusciel
Unification of API
Unification of API
@lukaszchrusciel
Shop vs
Shop vs Admin
Admin
@lukaszchrusciel
Shop
Shop
72 endpoints
72 endpoints
64% of read endpoints
64% of read endpoints
20% of resources have
20% of resources have
writable capabilities
writable capabilities
Admin
Admin
128 endpoints
128 endpoints
52% of read endpoints
52% of read endpoints
40% of them are never
40% of them are never
exposed in shop
exposed in shop
@lukaszchrusciel
Option #1
Option #1
Admin & Shop served together
Admin & Shop served together
/api/products/
@lukaszchrusciel
Findings
Findings
Available fields
Available fields
Complicated serialisation groups depending on logged in user
Requires granular access control
Requires granular access control
To not allow to access sensitive date for non-admins
Hard to define different identifiers
Hard to define different identifiers
We have resigned from them later
Good from REST perspective
Good from REST perspective
@lukaszchrusciel
Option #2
Option #2
Admin & Shop suffixed
Admin & Shop suffixed
/api/products/?admin
@lukaszchrusciel
Findings
Findings
Available fields
Available fields
Depending on logged in user
Requires granular access control
Requires granular access control
To not allow to access sensitive date for non-admins
Seems wrong from the REST perspective
Seems wrong from the REST perspective
If we add suffix for different representation
@lukaszchrusciel
Option #3
Option #3
Admin & Shop header split
Admin & Shop header split
/api/products/
Accept: application/vnd.sylius-
admin.api+json
@lukaszchrusciel
Findings
Findings
Available fields
Available fields
Depending on logged in user
Requires granular access control
Requires granular access control
To now allow to access sensitive date for non-
admins
REST compilant
REST compilant
@lukaszchrusciel
Option #4
Option #4
Admin & Shop prefixed
Admin & Shop prefixed
/api/shop/products/
/api/admin/products/
@lukaszchrusciel
Findings
Findings
Available fields
Available fields
Depending on logged in user
Straightforward access control
Straightforward access control
Just with security config
/
/ REST compilant
REST compilant
Disputable
/
/ Easily supported
Easily supported
By API Platform and by Open API spec
@lukaszchrusciel
QUIZ
QUIZ
@lukaszchrusciel
Conclusion
Conclusion
Custom headers are the
Custom headers are the
way to go
way to go
But
But
We chose the resource split
We chose the resource split
@lukaszchrusciel
API versioning
API versioning
@lukaszchrusciel
What we had
What we had
/api/v1
@lukaszchrusciel
WIP
WIP
/new-api/
@lukaszchrusciel
Option #1
Option #1
URL based versioning
URL based versioning
/api/v2
@lukaszchrusciel
Option #2
Option #2
Accept header with version
Accept header with version
Accept:
application/vnd.sylius.v1+json
@lukaszchrusciel
Option #3
Option #3
Custom header
Custom header
X-Sylius-API-Version: 1
@lukaszchrusciel
@lukaszchrusciel
QUIZ
QUIZ
@lukaszchrusciel
@lukaszchrusciel
Conclusion
Conclusion
API Evolution
API Evolution
Version in header
Version in header
We chose versioning in URL
We chose versioning in URL
@lukaszchrusciel
API flow
API flow
design
design
@lukaszchrusciel
Case #1
Case #1
Add to cart
Add to cart
@lukaszchrusciel
Simple product
Simple product
{
"product": "/api/products/42",
"quantity": 1
}
@lukaszchrusciel
Configurable product #1
Configurable product #1
{
"product": "/api/products/42",
"productVariant":
"/api/variants/42",
"quantity": 1
}
@lukaszchrusciel
Configurable product #2
Configurable product #2
{
"product": "/api/products/42",
"options": {
"SIZE": "SIZE_L",
"COLOR": "COLOR_BLUE"
},
"quantity": 1
}
@lukaszchrusciel
Let’s improve!
Let’s improve!
@lukaszchrusciel
Simple
Simple
product
product
{
"product":
"/api/products/42",
"quantity": 1
}
Configurable
Configurable
product
product
{
"product":
"/api/products/8",
"productVariant":
"/api/variants/864",
"quantity": 1
}
@lukaszchrusciel
Simple
Simple
product
product
{
"product":
"/api/products/42",
"productVariant":
"/api/variants/42",
"quantity": 1
}
Configurable
Configurable
product
product
{
"product":
"/api/products/8",
"productVariant":
"/api/variants/864",
"quantity": 1
}
@lukaszchrusciel
Simple & Configurable product
Simple & Configurable product
{
"product": "/api/products/8",
"productVariant":
"/api/variants/42",
"quantity": 1
}
@lukaszchrusciel
Simple & Configurable product
Simple & Configurable product
{
"productVariant":
"/api/variants/42",
"quantity": 1
}
@lukaszchrusciel
But what with options?
But what with options?
@lukaszchrusciel
Price matrix in UI
Price matrix in UI
or
or
Ask us
Ask us
@lukaszchrusciel
Case #2
Case #2
Order details
Order details
@lukaszchrusciel
@lukaszchrusciel
Apply coupon
PATCH /api/orders/TOKEN_VALUE
/apply-coupon
{
"couponCode": "CHRISTMAS_SALE"
}
@lukaszchrusciel
@lukaszchrusciel
Cart claiming & addressing
PATCH /api/orders/TOKEN_VALUE/address
{
"email": "test@example.com",
"billingAddress": {
"firstName": "Jane",
"lastName": "Doe",
"...": "..."
}
}
@lukaszchrusciel
Reasoning?
Reasoning?
We are used to this separation
We are used to this separation
Mockups “force” such design
Different data required on different pages
Different data required on different pages
Addressing requires state machine
Addressing requires state machine
transition
transition
Adding coupon requires recalculation
@lukaszchrusciel
What about order
What about order
update?
update?
@lukaszchrusciel
Order update
Order update
PUT
/api/orders/TOKEN_VALUE
{
"localeCode": "en_US"
}
@lukaszchrusciel
But it is just order
But it is just order
drafting!
drafting!
@lukaszchrusciel
Changed attitude
Changed attitude
UI Mockups should not force any
UI Mockups should not force any
design decision
design decision
We can preload data from more then one endpoint
Use partial update
Use partial update
Don’t use state machine where there
Don’t use state machine where there
is none
is none
@lukaszchrusciel
Order update
Order update
PUT /api/orders/TOKEN_VALUE
{
"email": "test@example.com",
"billingAddress": {
"firstName": "Jane",
"lastName": "Doe",
"...": "..."
},
"couponCode": "CHRISTMAS_SALE"
}
@lukaszchrusciel
Conclusion
Conclusion
Don't let mockups force
Don't let mockups force
you API design
you API design
@lukaszchrusciel
State transitions
State transitions
@lukaszchrusciel
Case
Case
Let’s cancel an
Let’s cancel an
order!
order!
@lukaszchrusciel
Considered option #1
Considered option #1
PATCH /api/orders/42/
{
"state": "cancelled"
}
@lukaszchrusciel
Considered option #2
Considered option #2
PATCH
/api/orders/42/cancel
{}
@lukaszchrusciel
@lukaszchrusciel
RESTful Archetypes
RESTful Archetypes
@lukaszchrusciel
RESTful Archetypes
RESTful Archetypes
Document
Document
/api/admin/orders/1
Collections
Collections
Server controlled /api/admin/orders
Store
Store
Client controlled /api/admin/orders/123
Controller
Controller
/api/admin/orders/1/cancel
1
Based on: REST API Design Rulebook by Mark Masse
1.
@lukaszchrusciel
Isn’t there a better way?
Isn’t there a better way?
@lukaszchrusciel
Considered option #3
Considered option #3
POST /api/orders-
cancellation-requests/
{}
@lukaszchrusciel
QUIZ
QUIZ
@lukaszchrusciel
Conclusion
Conclusion
Express your operation
Express your operation
as resources
as resources
@lukaszchrusciel
Takeaways
Takeaways Use ADRs
Use ADRs
And browse them from time to time
REST will be with us for the
REST will be with us for the
long time
long time
But GraphQL will be there as well
Custom logic? New API
Custom logic? New API
resource!
resource!
Let’s behave like a tax department!
Do not map HTML based
Do not map HTML based
websites to your API
websites to your API
I know, it was obvious
@lukaszchrusciel
Thank
Thank
you!
you!

BoilingFrogs - Rozterki i decyzje. Czego się nauczyliśmy projektując API Syliusa