The document discusses the design of a new Sylius API. It began development in 2020 and has covered 100% of shop endpoints and 70% of all endpoints. Various options were considered for API design decisions like versioning, calculating dynamic fields, and handling admin vs shop resources. Architecture decision records were used to document choices. Based on lessons learned, REST was found to be better than GraphQL by default, and calculated data is best handled with read models rather than on entities. UI mockups should not dictate API design, and custom operations are best modeled with new resource endpoints.
8. 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
13. 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]
14. 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]
15. 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]
16. 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]
17. 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]
18. 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]
19. 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]
50. 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
}
51. Version #2 - Read model
Version #2 - Read model
readonly class CartShippingMethod
{
public function __construct(
public string $code,
public ShippingMethodInterface
$shippingMethod,
public int $cost
) {
}
}
52. Version #3 - Dynamic field
Version #3 - Dynamic field
53. Version #3 - Dynamic field
Version #3 - Dynamic field
54. 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;
}
59. 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
61. 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
63. 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
65. 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
67. 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
69. 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
98. 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
101. But it is just order drafting!
But it is just order drafting!
102. Changed attitude
Changed attitude
UI Mockups should not force
UI Mockups should not force
any design decision
any design decision
We can preload data from more then one endpoint
Use partial update
Use partial update
Don’t use state machine
Don’t use state machine
where there is none
where there is none
103. Order update
Order update
PUT /api/v2/shop
/orders/TOKEN_VALUE/
{
"email": "test@example.com",
"billingAddress": {
"firstName": "Jane",
"lastName": "Doe",
"...": "..."
},
"couponCode": "CHRISTMAS_SALE"
}
105. Takeaways
Takeaways Use ADRs
Use ADRs
And browse them from time to time
REST will be with us for
REST will be with us for
the long time
the 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