8000 Add schema trait and async process docs · sablesoft/laravel-json-api@d35e21a · GitHub
[go: up one dir, main page]

Skip to content

Commit d35e21a

Browse files
committed
Add schema trait and async process docs
1 parent 96938f3 commit d35e21a

File tree

7 files changed

+341
-67
lines changed

7 files changed

+341
-67
lines changed

docs/features/async.md

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# Asynchronous Processing
2+
3+
The JSON API specification
4+
[provides a recommendation](https://jsonapi.org/recommendations/#asynchronous-processing)
5+
for how APIs can implement long running processes. For example, if the operation to create a
6+
resource takes a long time, it is more appropriate to process the creation using
7+
[Laravel's queue system](https://laravel.com/docs/queues)
8+
and return a `202 Accepted` response to the client.
9+
10+
This package provides an opt-in implementation of the JSON API's asynchronous processing recommendation
11+
that integrates with Laravel's queue. This works by storing information about the dispatched job
12+
in a database, and using Laravel's queue events to updating the stored information.
13+
14+
## Installation
15+
16+
### Migrations
17+
18+
By default this package does not run migrations to create the database tables required to store
19+
information on the jobs that have been dispatched by the API. You must therefore opt-in to the
20+
migrations in the `register` method of your `AppServiceProvider`:
21+
22+
```php
23+
<?php
24+
25+
namespace App\Providers;
26+
27+
use CloudCreativity\LaravelJsonApi\LaravelJsonApi;
28+
use Illuminate\Support\ServiceProvider;
29+
30+
class AppServiceProvider extends ServiceProvider
31+
{
32+
33+
// ...
34+
35+
public function register()
36+
{
37+
LaravelJsonApi::runMigrations();
38+
}
39+
40+
}
41+
```
42+
43+
### Migration Customisation
44+
45+
If you want to customise the migrations, you can publish them as follows:
46+
47+
```bash
48+
$ php artisan vendor:publish --tag="json-api-migrations"
49+
```
50+
51+
If you do this, you **must not** call `LaravelJsonApi::runMigrations()` in your service provider.
52+
53+
### Generate Resource Classes
54+
55+
You now need to generate JSON API classes for the resource type that will represent the asynchronous
56+
processes in your API. We do not provide these by default because the logic of how you want to page,
57+
filter, etc. your resources is specific to your own API. This also means you can serialize any
58+
attributes you want in the resource schema, and use your own API's convention for attribute names.
59+
60+
To generate the classes, run the following command:
61+
62+
```php
63+
$ php artisan make:json-api:resource -e queue-jobs
64+
```
65+
66+
> Replace `queue-jobs` in the above command if you want to call the resource something different.
67+
If you use a different name, you will need to change the `jobs.resource` config setting in your
68+
API's configuration file.
69+
70+
In the generated schema, you will need to add the `AsyncSchema` trait, for example:
71+
72+
```php
73+
use CloudCreativity\LaravelJsonApi\Queue\AsyncSchema;
74+
use Neomerx\JsonApi\Schema\SchemaProvider;
75+
76+
class Schema extends SchemaProvider
77+
{
78+
use AsyncSchema;
79+
80+
// ...
81+
}
82+
```
83+
84+
### Model Customisation
85+
86+
By default the implementation uses the `CloudCreativity\LaravelJsonApi\Queue\ClientJob` model.
87+
If you want to use a different model, then you can change this by editing the `jobs.model` config
88+
setting in your API's configuration file.
89+
90+
Note that if you use a different model, you may also want to customise the migration as described
91+
above.
92+
93+
If you are not extending the `ClientJob` model provided by this package, note that your custom
94+
model must implement the `CloudCreativity\LaravelJsonApi\Contracts\Queue\AsynchronousProcess`
95+
interface.
96+
97+
## Dispatching Jobs
98+
99+
For a Laravel queue job to appear as an asynchronous process in your API, you must add the
100+
`CloudCreativity\LaravelJsonApi\Queue\ClientDispatchable` trait to it and use this to dispatch
101+
the job.
102+
103+
For example:
104+
105+
```php
106+
namespace App\Jobs;
107+
108+
use CloudCreativity\LaravelJsonApi\Queue\ClientDispatchable;
109+
use Illuminate\Contracts\Queue\ShouldQueue;
110+
111+
class ProcessPodcast implements ShouldQueue
112+
{
113+
114+
use ClientDispatchable;
115+
116+
// ...
117+
}
118+
119+
```
120+
121+
The job can then be dispatched as follows:
122+
123+
```php
124+
/** @var \CloudCreativity\LaravelJsonApi\Queue\ClientJob $process */
125+
$process = ProcessPodcast::client($podcast)->dispatch();
126+
```
127+
128+
The object returned by the static `client` method extends Laravel's `PendingDispatch` class. This
129+
means you can use any of the normal Laravel methods. The only difference is you **must** call the
130+
`dispatch` method at the end of the chain so that you have access to the process that was stored
131+
and can be serialized into JSON by your API.
132+
133+
You can use this method of dispatching jobs in either
134+
[Controller Hooks](../basics/controllers.md) or within
135+
[Resource Adapters](../basics/adapters.md), depending on your preference.
136+
137+
### Dispatching in Controllers
138+
139+
You can use controller hooks to return asynchronous processes. For example, if you needed
140+
to process a podcast after creating a podcast model you could use the `created` hook:
141+
142+
```php
143+
use App\Podcast;
144+
use App\Jobs\ProcessPodcast;
145+
use CloudCreativity\LaravelJsonApi\Http\Controllers\JsonApiController;
146+
147+
class PodcastsController extends JsonApiController
148+
{
149+
150+
// ...
151+
152+
protected function created(Podcast $podcast)
153+
{
154+
return ProcessPodcast::client($podcast)->dispatch();
155+
}
156+
}
157+
```
158+
159+
> The `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting` and `deleted`
160+
hooks will be the most common ones to use for asynchronous processes.
161+
162+
### Dispatching in Adapters
163+
164+
If you prefer to dispatch your jobs in a resource adapters, then the adapters support returning
165+
asynchronous processes.
166+
167+
For example, to process a podcast after creating it:
168+
169+
```php
170+
namespace App\JsonApi\Podcasts;
171+
172+
use App\Jobs\ProcessPodcast;
173+
use CloudCreativity\LaravelJsonApi\Eloquent\AbstractAdapter;
174+
use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
175+
176+
class Adapter extends AbstractAdapter
177+
{
178+
179+
// ...
180+
181+
public function create(array $document, EncodingParametersInterface $parameters)
182+
{
183+
$podcast = parent::create($document, $parameters);
184+
185+
return ProcessPodcast::client($podcast)->dispatch();
186+
}
187+
}
188+
```
189+
190+
## Linking Processes to Created Resources
191+
192+
If a dispatched job creates a new resource (e.g. a new model), there is one additional step you will
193+
need to follow in the job's `handle` method. This is to link the stored process to the resource that was
194+
created as a result of the job completing successfully. The link must exist otherwise your API
195+
will not be able to inform a client of the location of the created resource once the job is complete.
196+
197+
You can easily create this link by calling the `didCreate` method that the `ClientDispatchable`
198+
trait adds to your job. For example:
199+
200+
```php
201+
namespace App\Jobs;
202+
203+
use CloudCreativity\LaravelJsonApi\Queue\ClientDispatchable;
204+
use Illuminate\Contracts\Queue\ShouldQueue;
205+
206+
class ProcessPodcast implements ShouldQueue
207+
{
208+
209+
use ClientDispatchable;
210+
211+
// ...
212+
213+
public function handle()
214+
{
215+
// ...logic to process a podcast
216+
217+
$this->didCreate($podcast);
218+
}
219+
}
220+
```
221+
222+
## HTTP Requests and Responses
223+
224+
Once you have followed the above instructions, you can now make HTTP requests and receive
225+
asynchronous process responses that following the
226+
[JSON API recommendation.](https://jsonapi.org/recommendations/#asynchronous-processing)
227+
228+
For example, a request to create a podcast would receive the following response:
229+
230+
```http
231+
HTTP/1.1 202 Accepted
232+
Content-Type: application/vnd.api+json
233+
Content-Location: http://homestead.local/podcasts/queue-jobs/1680e9a0-6643-42ab-8314-1f60f0b6a6b2
234+
235+
{
236+
"data": {
237+
"type": "queue-jobs",
238+
"id": "1680e9a0-6643-42ab-8314-1f60f0b6a6b2",
239+
"attributes": {
240+
"created-at": "2018-12-25T12:00:00",
241+
"updated-at": "2018-12-25T12:00:00"
242+
},
243+
"links": {
244+
"self": "/podcasts/queue-jobs/1680e9a0-6643-42ab-8314-1f60f0b6a6b2"
245+
}
246+
}
247+
}
248+
```
249+
250+
> You are able to include a lot more attributes by adding them to your queue-jobs resource schema.
251+
252+
To check the status of the job process, a client can send a request to the `Content-Location` given
253+
in the previous response:
254+
255+
```http
256+
GET /podcasts/queue-jobs/1680e9a0-6643-42ab-8314-1f60f0b6a6b2 HTTP/1.1
257+
Accept: application/vnd.api+json
258+
```
259+
260+
If the job is still pending, a `200 OK` response will be returned and the content will contain the
261+
`queue-jobs` resource.
262+
263+
When the job process is done, the response will return a `303 See Other` status. This will contain
264+
a `Location` header giving the URL of the created podcast resource:
265+
266+
```http
267+
HTTP/1.1 303 See other
268+
Content-Type: application/vnd.api+json
269+
Location: http://homestead.local/podcasts/4577
270+
```

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pages:
3131
- Updating Relationships: crud/relationships.md
3232
- Deleting Resources: crud/deleting.md
3333
- Digging Deeper:
34+
- Asynchronous Processing: features/async.md
3435
- Broadcasting: features/broadcasting.md
3536
- Errors: features/errors.md
3637
- Helpers: features/helpers.md

src/Contracts/Queue/AsynchronousProcess.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
interface AsynchronousProcess
1515
{
1616

17+
/**
18+
* Get the resource type that the process relates to.
19+
*
20+
* @return string
21+
*/
22+
public function getResourceType(): string;
23+
1724
/**
1825
* Get the location of the resource that the process relates to, if known.
1926
*

src/Queue/AsyncSchema.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace CloudCreativity\LaravelJsonApi\Queue;
4+
5+
use CloudCreativity\LaravelJsonApi\Contracts\Queue\AsynchronousProcess;
6+
7+
trait AsyncSchema
8+
{
9+
10+
/**
11+
* @return string
12+
*/
13+
public function getResourceType()
14+
{
15+
$api = property_exists($this, 'api') ? $this->api : null;
16+
17+
return json_api($api)->getJobs()->getResource();
18+
}
19+
20+
/**
21+
* @param AsynchronousProcess|null $resource
22+
* @return string
23+
*/
24+
public function getSelfSubUrl($resource = null)
25+
{
26+
if (!$resource) {
27+
return '/' . $this->getResourceType();
28+
}
29+
30+
return sprintf(
31+
'/%s/%s/%s',
32+
$resource->getResourceType(),
33+
$this->getResourceType(),
34+
C662 $this->getId($resource)
35+
);
36+
}
37+
}

src/Queue/ClientJob.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ public static function boot()
8787
});
8888
}
8989

90+
/**
91+
* @inheritDoc
92+
*/
93+
public function getResourceType(): string
94+
{
95+
if (!$type = $this->resource_type) {
96+
throw new RuntimeException('No resource type set.');
97+
}
98+
99+
return $type;
100+
}
101+
90102
/**
91103
* @inheritDoc
92104
*/

0 commit comments

Comments
 (0)
0