Skip to main content
Dilemmas and
Dilemmas and
decisions
decisions
What we’ve learned designing the
What we’ve learned designing the
new Sylius API
new Sylius API
QUIZ
QUIZ
Introduction
Introduction
Started in 2020
Started in 2020
1.12 with AP 2.7
1.12 with AP 2.7
(since 31st of Oct)
1.13 stabilized with AP 3.0
1.13 stabilized with AP 3.0
~100% of
~100% of Shop endpoints
Shop endpoints
covered
covered
~70% of all endpoints covered
Decisions &
Decisions &
consequences
consequences
Strategic design
Strategic design
ADRs
ADRs
QUIZ
QUIZ
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]
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]
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]
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]
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]
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]
Conclusion
Conclusion
Architecture
Architecture
Decision
Decision
Records FTW
Records 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]
GraphQL vs REST
GraphQL vs REST
2020
2020
GraphQL
GraphQL
Is it still?
Is it still?
Is it not?
Is it not?
GraphQL
GraphQL
Solves over fetching
Solves over fetching
and under fetching
and under fetching
By design
GraphQL
GraphQL
Sends everything with
Sends everything with
POST
POST
It is possible to do it with GET
GraphQL
GraphQL
Typed, nice
Typed, nice
documentation
documentation
out of the box
GraphQL
GraphQL
Gracefully deprecation
Gracefully deprecation
of queries
of queries
Which was not possible with default
REST
REST
REST
May solve over fetching
May solve over fetching
and under fetching
and under fetching
With sparefields sets and/or Vulcain
REST
REST
Takes advantage of 30
Takes advantage of 30
years of web cache
years of web cache
development
development
Fake data institute™
REST
REST
Typed, nice
Typed, nice
documentation
documentation
With OpenAPI
REST
REST
Gracefully deprecation
Gracefully deprecation
of queries
of queries
With OpenAPI and HTTP Headers
Quiz
Quiz
Conclusion
Conclusion
REST was a better default
REST was a better default
for us
for us
Resources
Resources
design
design
State transitions
State transitions
Case
Case
Let’s cancel an
Let’s cancel an
order!
order!
Considered option #1
Considered option #1
PATCH /api/orders/42/
{
"state": "cancelled"
}
Considered option #2
Considered option #2
PATCH
/api/orders/42/cancel
{}
RESTful Archetypes
RESTful Archetypes
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.
Isn’t there a better way?
Isn’t there a better way?
Considered option #3
Considered option #3
POST /api/orders-
cancellation-requests/
{}
QUIZ
QUIZ
Conclusion
Conclusion
Express your operation as
Express your operation as
resources
resources
Calculated data
Calculated data
Case
Case
Cost of the
Cost of the
shipment
shipment
Version #1 - Adding fields on entity
Version #1 - Adding fields on entity
class ShippingMethod
{
/** rest of methods */
public ?int $cost; // not used in
business code
}
Version #2 - Read model
Version #2 - Read model
readonly class CartShippingMethod
{
public function __construct(
public string $code,
public ShippingMethodInterface
$shippingMethod,
public int $cost
) {
}
}
Version #3 - Dynamic field
Version #3 - Dynamic field
Version #3 - Dynamic field
Version #3 - Dynamic field
Version #3 - Dynamic field
Version #3 - Dynamic field
public function normalize(
$object,
$format = null,
array $context = []
) {
// $data creation
$calculator = $this->shippingCalculators->get($object-
>getCalculator());
$data['price'] = $calculator->calculate(
$shipment,
$object->getConfiguration()
);
return $data;
}
QUIZ
QUIZ
Conclusion
Conclusion
Read models are default
Read models are default
way to go
way to go
High level API
High level API
design
design
Unification of API
Unification of API
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
Option #1
Option #1
Admin & Shop served together
Admin & Shop served together
/api/products/
Findings
Findings
Available fields
Available fields
Complicated serialisation groups depending on logged in
user
Requirement to define granular access control
Requirement to define granular access control
To now allow to access sensitive date for non-admins
Hard to define identifiers
Hard to define identifiers
We have resigned from them later
Good from REST perspective
Good from REST perspective
Option #2
Option #2
Admin & Shop suffixed
Admin & Shop suffixed
/api/products/?admin
Findings
Findings
Available fields
Available fields
Depending on logged in user
Requirement to define granular access control
Requirement to define granular access control
To now 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
Option #3
Option #3
Admin & Shop header split
Admin & Shop header split
/api/products/
Accept: application/vnd.sylius-
admin.api+json
Findings
Findings
Available fields
Available fields
Depending on logged in user
Requirement to define granular access control
Requirement to define granular access control
May be mitigated with Voters
REST compilant
REST compilant
Not easily supported
Not easily supported
By API Platform and Open API spec
Option #4
Option #4
Admin & Shop prefixed
Admin & Shop prefixed
/api/shop/products/
/api/admin/products/
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
kind of by API Platform and fully by Open API spec
QUIZ
QUIZ
Conclusion
Conclusion
Custom headers are the
Custom headers are the
way to go
way to go
But
But
Resource split was the
Resource split was the
best solution for us
best solution for us
API versioning
API versioning
What we had
What we had
/api/v1
WIP
WIP
/new-api/
Option #1
Option #1
URL based versioning
URL based versioning
/api/v2
Option #2
Option #2
Accept header with version
Accept header with version
Accept:
application/vnd.sylius.v1+json
Option #3
Option #3
Custom header
Custom header
X-Sylius-API-Version: 1
QUIZ
QUIZ
Conclusion
Conclusion
API Evolution
API Evolution
Version in header
Version in header
We chose versioning in URL
We chose versioning in URL
API flow design
API flow design
Case #1
Case #1
Add to cart
Add to cart
Simple product
Simple product
{
"product": "/api/products/42",
"quantity": 1
}
Configurable product #1
Configurable product #1
{
"product": "/api/products/42",
"productVariant": "/api/variants/42",
"quantity": 1
}
Configurable product #2
Configurable product #2
{
"product": "/api/products/42",
"options": {
"SIZE": "SIZE_L",
"COLOR": "COLOR_BLUE"
},
"quantity": 1
}
Let’s improve!
Let’s improve!
Simple
Simple
product
product
{
"product":
"/api/products/42",
"quantity": 1
}
Configurable
Configurable
product
product
{
"product":
"/api/products/8",
"productVariant":
"/api/variants/864",
"quantity": 1
}
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
}
Simple & Configurable product
Simple & Configurable product
{
"product": "/api/products/8",
"productVariant": "/api/variants/42",
"quantity": 1
}
Simple & Configurable product
Simple & Configurable product
{
"productVariant": "/api/variants/42",
"quantity": 1
}
But what with options?
But what with options?
Price matrix in UI
Price matrix in UI
or
or
Ask us
Ask us
Case #2
Case #2
Order details
Order details
Apply coupon
PATCH /api/orders/TOKEN_VALUE
/apply-coupon
{
"couponCode": "CHRISTMAS_SALE"
}
Cart claiming & addressing
PATCH /api/
orders/TOKEN_VALUE/address
{
"email": "test@example.com",
"billingAddress": {
"firstName": "Jane",
"lastName": "Doe",
"...": "..."
}
}
Reasoning?
Reasoning?
We are used to this separation
We are used to this separation
Mockups “force” such design
Different data required on
Different data required on
different pages
different pages
Addressing requires state
Addressing requires state
machine transition
machine transition
While coupon appliance cart processing
What about order update?
What about order update?
Order update
Order update
PUT
/api/orders/TOKEN_VALUE
{
"localeCode": "en_US"
}