8000 Merge branch 'feature/async' into async-with-cn · CodingSeo/laravel-json-api@e37ff4f · GitHub
[go: up one dir, main page]

Skip to content

Commit e37ff4f

Browse files
committed
Merge branch 'feature/async' into async-with-cn
2 parents 04a4c93 + d35e21a commit e37ff4f

28 files changed

+854
-142
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ update request.
1717
`type` or `id` fields.
1818
- JSON API specification validation will now fail if the `attributes` and `relationships` members have
1919
common field names, as field names share a common namespace.
20+
- Can now return `Responsable` instances from controller hooks.
2021

2122
### Changed
2223
- [#248](https://github.com/cloudcreativity/laravel-json-api/pull/248)

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"illuminate/pagination": "5.5.*|5.6.*|5.7.*",
3434
"illuminate/support": "5.5.*|5.6.*|5.7.*",
3535
"neomerx/json-api": "^1.0.3",
36+
"ramsey/uuid": "^3.0",
3637
"symfony/psr-http-message-bridge": "^1.0",
3738
"zendframework/zend-diactoros": "^1.3"
3839
},

docs/basics/api.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,22 @@ The default API name is `default`. You can change the default name via the JSON
99
the following to the `boot()` method of your `AppServiceProvider`:
1010

1111
```php
12-
public function boot()
12+
<?php
13+
14+
namespace App\Providers;
15+
16+
use CloudCreativity\LaravelJsonApi\LaravelJsonApi;
17+
use Illuminate\Support\ServiceProvider;
18+
19+
class AppServiceProvider extends ServiceProvider
1320
{
14-
JsonApi::defaultApi('v1');
21+
public function boot()
22+
{
23+
LaravelJsonApi::defaultApi('v1');
24+
}
25+
26+
// ...
27+
1528
}
1629
```
1730

docs/features/async.md

Lines changed: 270 additions & 0 deletions
F438
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/Adapter/AbstractResourceAdapter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,9 @@ protected function fillRelated($record, ResourceObject $resource, EncodingParame
232232
protected function fillAndPersist($record, ResourceObject $resource, EncodingParametersInterface $parameters)
233233
{
234234
$this->fill($record, $resource, $parameters);
235+
$async = $this->persist($record);
235236

236-
if ($async = $this->persist($record)) {
237+
if ($async instanceof AsynchronousProcess) {
237238
return $async;
238239
}
239240

src/Api/Api.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ class Api
8383
private $url;
8484

8585
/**
86-
* @var string|null
86+
* @var Jobs
8787
*/
88-
private $jobFqn;
88+
private $jobs;
8989

9090
/**
9191
* @var string|null
@@ -126,7 +126,7 @@ class Api
126126
* @param $apiName
127127
* @param Codecs $codecs
128128
* @param Url $url
129-
* @param string|null $jobFqn
129+
* @param Jobs $jobs
130130
* @param bool $useEloquent
131131
* @param string|null $supportedExt
132132
* @param array $errors
@@ -137,7 +137,7 @@ public function __construct(
137137
$apiName,
138138
Codecs $codecs,
139139
Url $url,
140-
$jobFqn = null,
140+
Jobs $jobs,
141141
$useEloquent = true,
142142
$supportedExt = null,
143143
array $errors = []
@@ -151,7 +151,7 @@ public function __construct(
151151
$this->name = $apiName;
152152
$this->codecs = $codecs;
153153
$this->url = $url;
154-
$this->jobFqn = $jobFqn;
154+
$this->jobs = $jobs;
155155
$this->useEloquent = $useEloquent;
156156
$this->supportedExt = $supportedExt;
157157
$this->errors = $errors;
@@ -223,13 +223,11 @@ public function getUrl()
223223
}
224224

225225
/**
226-
* Get the fully qualified name of the class to use for storing client jobs.
227-
*
228-
* @return string
226+
* @return Jobs
229227
*/
230-
public function getJobFqn()
228+
public function getJobs()
231229
{
232-
return $this->jobFqn ?: ClientJob::class;
230+
return $this->jobs;
233231
}
234232

235233
/**

0 commit comments

Comments
 (0)
0