How to solve the n+1 problem created by fractal
--
The popular package, thephpleague/fractal aims to protect your API consumers from output inconsistencies and also provide an elegant way to include relationships for complex data structures.
example.com?include=foo,foo.bar,foo.bar.baz,bla
for 100 records per page == 400 queries!
The problem with the includes is that all relations are being lazily loaded.
TL;DR use Laravel 5.5+ Eloquent Resources and Spatie’s query builder
The getId()
, getContent()
getters were created manually inside the Review
model, in order to decouple database schema from code.
For this example, the includes may look like:
example.com/user/101/reviews?include=product,product.merchant
Besides the nested data key, the additional database queries are killing the performance.
There are some workarounds, for example, we could use eagerly loaded relations, but the question is “how many?”. What happens when the front-end requires more includes? Do we eager load everything, even nested relations?
Lazy eager load means more than one request and, moreover, you have to implement it manually, to override the TransformerAbstract
class, callIncludeMethod
, after fractal has checked that a given include is valid.
Joins are too expensive, especially for large tables.
The solution I found consists of this couple:
Laravel 5.5 introduced Eloquent Resources, which basically are a sort of native transformers on steroids: https://laravel.com/docs/5.6/eloquent-resources#conditional-relationships
Spatie’s query builder https://github.com/spatie/laravel-query-builder whose ?include=product,product.merchant enables you to get rid of the fractal package.
Why on steroids? The conditional relationship enables the application to eagerly load JUST the requested relationships, namely product
and product.merchant
only when they are requested.
Let’s suppose the consumer does not require the product.merchant
nested relation
example.com/user/101/reviews?include=product
Then, the merchant
relation is not loaded and the key is missing entirely from the generated output! How cool is that?
example.com/user/101/reviews?include=not-whitelisted.nested-relation
Returns status code 400, with the message: Given include(s) `not-whitelisted.nested-relation` are not allowed. Allowed include(s) are `product, product.merchant`
Another benefit of Laravel’s Resource is that you can structure the output better. No more nested data
keys on every included relation
Spatie’s query builder can also filter, sort and restrict the nested includes (which fractal can’t do). Moreover, the orWhere
statement won’t fool the application to display all protected resources.