Plan for Version 2
We are starting to turn our attention to version 2 of this package. This issue describes the thinking and invites any comments/suggestions about the approach. We're aware a lot of people are using this package, so we want to get this right.
Background
When we first wrote this Laravel package, it was based on a framework-agnostic implementation because we weren't just using it in a Laravel application. One of the best decisions of the 1.0.0-alpha/beta
series was to move the code into this package and develop it as a solely-Laravel package.
During the 1.0
development we started making it feel more like a Laravel package, however there was a limit to how much we could do that without introducing more breaking changes than we were comfortable with.
The other bit of background is that we use the neomerx/json-api
package, predominantly for the encoding. That package has a great attention on making encoding performant, so we still want to use it. However, we are not entirely confident that decisions made on that package fit with our overall approach and unfortunately recent changes to that package make extending some of the internals complex.
Additionally, exposing the neomerx interfaces to the Laravel developer makes this package feel less intuitively like a Laravel package - as there's more code to wrap your head around.
Aims
Based on this background, there are two main things we want 2.0
to achieve:
- Make the use of this package align closely with existing Laravel approaches (wherever possible); and
- Hide the use of the neomerx package so it becomes an internal implementation detail, rather than something the developer needs to know about.
Laravel Alignment
Currently we have:
- App
- Http
- Controllers
- Api
- PostsController (optional)
- JsonApi
- Posts
- Adapter
- Schema
- Validators
- Authorizer (optional)
- ContentNegotiator (optional)
This will still work on 2.0
, requiring only minor interface changes and PHP 7 type-hinting to upgrade. Any code relating to the implementation will be marked as deprecated and removed in 3.0
.
Our new style for 2.0
will be this:
- App
- Http
- Controllers
- Api
- PostController (optional)
- Resources
- JsonApi
- PostAdapter
- PostResource
- PostCollection (optional)
- Requests
- JsonApi
- PostRequest
- PostQuery
The reason for the JsonApi
namespaces is to prevent collisions with existing classes, e.g
an Eloquent PostResource
, or an existing PostRequest
form request. It also makes it entirely clear which classes are to do with your JSON-API implementation.
There will be an option to sub-namespace things if you have multiple JSON APIs. E.g. if we have
a v1
and v2
API, the resources will be JsonApi\V1\PostResource
, JsonApi\V2\PostResource
etc.
This means the following differences from the current implementation:
Schema
is replaced by PostResource
, which will have a similar style to Eloquent resources. Similar is intentionally used, because the Eloquent approach will not fully work for JSON API, and it has some aspects that would undermine the performance of the JSON API encoder. Resource classes will implement Responsable
and JsonSerialize
so that they can be used in other contexts as required.
Validators
is split into PostRequest
for validating request JSON content and PostQuery
to validate the query parameters for a response that will contain posts
resources.
Authorizers
cease to exist and use standard Laravel authorization via middleware, form request authorize
methods, controller middleware and controller helpers.
ContentNegotiators
are removed but the use-case is fully supported (and documented). Different media-types will be easier to wire in as there will be a specific PostRequest
and PostQuery
on which validation can be customised or skipped.
- Adapters will return either the resource class, or the resource collection class, or
null
. These will allow the adapter to provide meta
and links
as needed.
- Standard Laravel route bindings will be used.
- The default controller will be composed using traits, allowing a developer to create their own custom controllers if they want. We will offload the running of the HTTP handling logic into a
Server
class, so it's easier to call our default handling of JSON API requests from within a custom controller if needed.
- Controller and adapter hooks will be replaced with standard Laravel events. For resource update events, we will provide data about the before and after state, allowing listeners to decide to do something if a resource field has changed.
- We will enable auto-generation of API docs by inspecting defined routes and the request/query classes. We will show example resources by serializing models created via Eloquent factories. This means the docs output will need to be created in development, and can then be committed to version control.
As an example, our new default controller would look like this:
<?php
namespace CloudCreatvity\LaravelJsonApi\Http\Controllers;
use CloudCreativity\LaravelJsonApi\Http\Actions;
class ResourceController
{
use Actions\Index;
use Actions\Store;
use Actions\Show;
use Actions\Update;
use Actions\Destroy;
use Actions\Related\Show;
use Actions\Relationships\Show;
use Actions\Relationships\Replace;
use Actions\Relationships\Add;
use Actions\Relationships\Remove;
}
The action names here match Laravel's default names for resource controllers - except for relationships, as they are a JSON API concept.
This means that if the developer wanted to write their own controller, changing the method signatures of some of the actions, they could compose their own controller using our traits - but omit the ones they want to change. E.g.
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\JsonApi\PostRequest;
use App\Http\Resources\JsonApi\PostQuery;
use CloudCreativity\LaravelJsonApi\Http\Actions;
use CloudCreativity\LaravelJsonApi\Facades\JsonApi;
use Illuminate\Contracts\Support\Responsable;
class PostController extends Controller
{
// default actions, e.g.
use Actions\Index;
public function store(\Illuminate\Http\Request $request): Responsable
{
if ($something) {
// return something custom.
}
// use our standard implementation via the `server()` helper.
// lazily resolving the request/query classes means JSON API validation only happens now.
return JsonApi::server()->store(
app(PostRequest::class),
app(PostQuery::class)
);
}
}
Outside of HTTP requests, you can query your resources using a fluent JSON API query syntax. This effectively allows you to run the logic within your adapters in a style familiar to a Laravel Query Builder (but using JSON API terms, such as include
). Say we wanted to get a PostResource
from a post id:
$resource = JsonApi::resources()->posts()->include('comments', 'author')->find($postId);
return \json_encode($resource);
Neomerx Package
We are currently using v1 of the neomerx package, but it is currently on v3 - which contains a lot of performance improvements. We therefore desperately need to upgrade.
However, as the current implementation involves developers extending or type-hinting classes/interfaces from the neomerx package, this change will be extremely breaking.
As we plan to change our use of the neomerx package to an internal implementation detail, 2.0
of our package will set the neomerx Composer constraint to ^1.0.3|^3.0
. We will detect the version installed, caching the answer in config for performance reasons, and adjust the implementation internally to use either.
Developers will therefore be able to upgrade to 2.0
without changing their existing Schemas
etc. And can then upgrade to neomerx v3 once they have converted to the new PostResource
pattern described above.
How Can You Help?
Provide comments on this approach and highlight potential problems, so that we know whether we're on the right track!
We think that with these changes, this package will be one of the best options out there for building standard-compliant APIs in Laravel. So we could really do with a great website with docs that follow the Laravel docs sub-headings (i.e. so that they are instantly familiar to Laravel developers). If you're able to help out with a public website, let me know.
feature