How to solve the n+1 problem created by fractal

Unseen Heroes
3 min readMay 22, 2018

--

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 Fractal transformer

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
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.

Review, Product and Merchant resources
The controller
Spatie query builder + JSON API paginate
The final output
Hooray! The queries

--

--