2
2
3
3
Read about the [ Lucid Architecture Concept] ( https://medium.com/vine-lab/the-lucid-architecture-concept-ad8e9ed0258f ) .
4
4
5
+ - [ Installation] ( #installation )
6
+ - [ Introduction] ( #introduction )
7
+ - [ Components] ( #components )
8
+ - [ Service] ( #service )
9
+ - [ Feature] ( #feature )
10
+ - [ Job] ( #job )
11
+ - [ Data] ( #data )
12
+ - [ Foundation] ( #foundation )
13
+ - [ Getting Started] ( #getting-started )
14
+ - [ Microservices] ( #microservices )
15
+
5
16
## Installation
6
17
### 5.3
7
18
To start your project with Lucid right away, run the following command:
@@ -24,6 +35,281 @@ composer create-project lucid-arch/laravel=5.2.x my-project-5.2
24
35
composer create-project lucid-arch/laravel=5.1.x my-project-5.1
25
36
```
26
37
38
+ ## Introduction
39
+ The Lucid Architecture is a software architecture that helps contain applications' code bases as they grow
40
+ to not become overwhelmingly unmaintainable, gets us rid of code rot that will later become legacy code, and translate
41
+ the day-to-day language such as Feature and Service into actual, physical code.
42
+
43
+ ### Components
44
+
45
+ #### Directory Structure
46
+ ```
47
+ src
48
+ ├── Data
49
+ ├── Domains
50
+ └── * domain name *
51
+ ├── Jobs
52
+ ├── Foundation
53
+ └── Services
54
+ └── * service name *
55
+ ├── Console
56
+ ├── Features
57
+ ├── Http
58
+ ├── Providers
59
+ ├── Tests
60
+ ├── database
61
+ └── resources
62
+ ```
63
+
64
+ | Component | Path | Description |
65
+ | ---------| --------| ----------- |
66
+ | Service | src/Service/[ service] | Place for the [ Services] ( #service ) |
67
+ | Feature | src/Services/[ service] /Features/[ feature] | Place for the [ Features] ( #feature ) of [ Services] ( #service ) |
68
+ | Job | src/Domains/[ domain] /Jobs/[ job] | Place for the [ Jobs] ( #job ) that expose the functionalities of Domains |
69
+ | Data | src/Data | Place for models, repositories, value objects and anything data-related |
70
+ | Foundation | src/Foundation | Place for foundational (abstract) elements used across the application |
71
+
72
+
73
+ #### Service
74
+ Each part of an application is a service (i.e. Api, Web, Backend). Typically, each of these will have their way of
75
+ handling and responding to requests, implementing different features of our application, hence, each of them will have
76
+ their own routes, controllers, features and operations. A Service will seem like a sub-installation of a Laravel
77
+ application, though it is just a logical grouping than anything else.
78
+
79
+ To better understand the concept behind Services, think of the terminology as:
80
+ "Our application exposes data through an Api service", "You can manipulate and manage the data through the Backend service".
81
+
82
+ One other perk of using Lucid is that it makes the transition process to a Microservices architecture simpler,
83
+ when the application requires it. See [ Microservices] ( #microservices ) .
84
+
85
+ ##### Service Directory Structure
86
+
87
+ Imagine we have generated a service called ** Api** , can be done using the ` lucid ` cli by running:
88
+
89
+ > You might want to [ Setup] ( #setup ) to be able to use the ` lucid ` command.
90
+
91
+ ```
92
+ lucid make:service api
93
+ ```
94
+
95
+ We will get the following directory structure:
96
+
97
+ ```
98
+ src
99
+ └── Services
100
+ └── Api
101
+ ├── Console
102
+ ├── Features
103
+ ├── Http
104
+ │ ├── Controllers
105
+ │ ├── Middleware
106
+ │ ├── Requests
107
+ │ └── routes.php
108
+ ├── Providers
109
+ │ ├── ApiServiceProvider.php
110
+ │ └── RouteServiceProvider.php
111
+ ├── Tests
112
+ │ └── Features
113
+ ├── database
114
+ │ ├── migrations
115
+ │ └── seeds
116
+ └── resources
117
+ ├── lang
118
+ └── views
119
+ └── welcome.blade.php
120
+ ```
121
+
122
+ #### Feature
123
+
124
+ A Feature is exactly what a feature is in our application (think Login feature, Search for hotel feature, etc.) as a class.
125
+ Features are what the Services' controllers will serve, so our controllers will end up having only one line in our methods, hence,
126
+ the thinnest controllers ever! Here's an example of generating a feature and serving it through a controller:
127
+
128
+ > You might want to [ Setup] ( #setup ) to be able to use the ` lucid ` command.
129
+
130
+ > IMPORTANT! You need at least one service to be able to host your features. In this example we are using the Api
131
+ service generated previously, refered to as ` api ` in the commands.
132
+
133
+ ```
134
+ lucid make:feature SearchUsers api
135
+ ```
136
+
137
+ And we will have ` src/Services/Api/Features/SearchUsersFeature.php ` and its test ` src/Services/Api/Tests/Features/SearchUsersFeatureTest.php ` .
138
+
139
+ Inside the Feature class there's a ` handle ` method which is the method that will be called when we dispatch that feature,
140
+ and it supports dependency injection, which is the perfect place to define your dependencies.
141
+
142
+ Now we need a controller to serve that feature:
143
+ ```
144
+ lucid make:controller user api
145
+ ```
146
+
147
+ And our ` UserController ` is in ` src/Services/Api/Http/Controllers/UserController.php ` and to serve that feature,
148
+ in a controller method we need to call its ` serve ` method as such:
149
+
150
+ ```
151
+ namespace App\Services\Api\Http\Controllers;
152
+
153
+ use App\Services\Api\Features\SearchUsersFeature;
154
+
155
+ class UserController extends Controller
156
+ {
157
+ public function index()
158
+ {
159
+ return $this->serve(SearchUsersFeature::class);
160
+ }
161
+ }
162
+ ```
163
+
164
+ #### Job
165
+ A Job is responsible for one element of execution in the application, and play the role of a step in the accomplishment
166
+ of a feature. They are the stewards of reusability in our code.
167
+
168
+ Jobs live inside Domains, which requires them to be abstract, isolated and independent from any other job be it
169
+ in the same domain or another - whatever the case, no Job should dispatch another Job.
170
+
171
+ They can be ran by any Feature from any Service, and it is the * only way* of communication between services and domains.
172
+
173
+ * Example:* Our ` SearchUsersFeature ` has to perform the following steps:
174
+
175
+ - Validate user search query
176
+ - Log the query somewhere we can look at later
177
+ - If results were found
178
+ - Log the results for later reference (async)
179
+ - Increment the number of searches on the found elements (async)
180
+ - Return results
181
+ - If no results were found
182
+ - Log the query in a "high priority" log so that it can be given more attention
183
+
184
+ Each of these steps will have a job in its name, implementing only that step. They must be generated in their corresponding
185
+ domains that they implement the functionality of, i.e. our ` ValidateUserSearchQueryJob ` has to do with user input,
186
+ hence it should be in the ` User ` domain. While logging has nothing to do with users and might be used in several other
187
+ places so it gets a domain of its own and we generate the ` LogSearchResultsJob ` in that ` Log ` domain.
188
+
189
+ To generate a Job, use the ` make:job <job> <domain> ` command:
190
+
191
+ ```
192
+ lucid make:job SearchUsersByName user
193
+ ```
194
+
195
+ Similar to Features, Jobs also implement the ` handle ` method that gets its dependencies resolved, but sometimes
196
+ we might want to pass in input that doesn't necessarily have to do with dependencies, those are the params of the job's
197
+ constructor, specified here in ` src/Domains/User/Jobs/SearchUsersByNameJob.php ` :
198
+
199
+ ``` php
200
+ namespace App\Domains\User\Jobs;
201
+
202
+ use Lucid\Foundation\Job;
203
+
204
+ class SearchUsersByNameJob extends Job
205
+ {
206
+ private $query;
207
+ private $limit;
208
+
209
+ public function __construct($query, $limit = 25)
210
+ {
211
+ $this->query = $query;
212
+ $this->limit = $limit;
213
+ }
214
+
215
+ public function handle(User $user)
216
+ {
217
+ return $user->where('name', $this->query)->take($this->limit)->get();
218
+ }
219
+ }
220
+ ```
221
+
222
+ Now we need to run this and the rest of the steps we mentioned in ` SearchUsersFeature::handle ` :
223
+
224
+ ``` php
225
+ public function handle(Request $request)
226
+ {
227
+ // validate input - if not valid the validator should
228
+ // throw an exception of InvalidArgumentException
229
+ $this->run(new ValidateUserSearchQueryJob($request->input()));
230
+
231
+ $results = $this->run(SearchUsersFeature::class, [
232
+ 'query' => $request->input('query'),
233
+ ]);
234
+
235
+ if (empty($results)) {
236
+ $this->run(LogEmptySearchResultsJob::class, [
237
+ 'date' => new DateTime(),
238
+ 'query' => $request->query(),
239
+ ]);
240
+
241
+ $response = $this->run(new RespondWithJsonErrorJob('No users found'));
242
+ } else {
243
+ // this job is queueable so it will automatically get queued
244
+ // and dispatched later.
245
+ $this->run(LogUserSearchJob::class, [
246
+ 'date' => new DateTime(),
247
+ 'query' => $request->input(),
248
+ 'results' => $results->lists('id'), // only the ids of the results are required
249
+ ]);
250
+
251
+ $response = $this->run(new RespondWithJsonJob($results));
252
+ }
253
+
254
+ return $response;
255
+ }
256
+ ```
257
+
258
+ As you can see, the sequence of steps is clearly readable with the least effort possible when following each ` $this->run `
259
+ call, and the signatures of each Job are easy to understand.
260
+
261
+ A few things to note regarding the implementation above:
262
+
263
+ - There is no difference between running a job by instantiating it like ` $this->run(new SomeJob) ` and
264
+ passing its class name ` $this->run(SomeJob::class) `
265
+ it is simply a personal preference for readability and writing less code, when a Job takes only one parameter
266
+ it's instantiated.
267
+ - The order of parameters that we use when calling a job with its class name is irrelevant to their order in the
268
+ Job's constructor signature. i.e.
269
+ ```php
270
+ $this->run(LogUserSearchJob::class, [
271
+ 'date' => new DateTime(),
272
+ 'query' => $request->input(),
273
+ 'resultIds' => $results->lists('id'), // only the ids of the results are required
274
+ ] );
275
+ ```
276
+ ```php
277
+ class LogUserSearchJob
278
+ {
279
+ public function __ construct($query, array $resultIds, DateTime $date)
280
+ {
281
+ // ...
282
+ }
283
+ }
284
+ ```
285
+ This will work perfectly fine, as long as the key name (` 'resultIds' => ... ` ) is the same as the variable's name in the constructor (` $resultIds ` )
286
+ - Of course, we need to create and import (` use ` ) our Job classes with the correct namespaces, but we won't do that here
287
+ since this is only to showcase and not intended to be running, for a working example see [ Getting Started] ( #getting-started ) .
288
+
289
+ ## Data
290
+ Data is not really a component, more like a directory for all your data-related classes such as Models, Repositories,
291
+ Value Objects and anything that has to do with data (algorithms etc.).
292
+
293
+ ## Foundation
294
+ This is a place for foundational elements that act as the most abstract classes that do not belong in any of the
295
+ components, currenly holds the ` ServiceProvider ` which is the link between the services and the framework (Laravel).
296
+ You might never need or use this directory for anything else, but in case you encountered a case where a class
297
+ needs to be shared across all components and does belong in any, feel free to use this one.
298
+
299
+ Evey service must be registered inside the foundation's service provider after being created for laravel to know about it,
300
+ simply add ` $this->app->register([service name]ServiceProvider::class); ` to the ` register ` methods of the
301
+ foundation's ` ServiceProvider ` . For example, with an Api Service:
302
+
303
+ ``` php
304
+ // ...
305
+ use App\Services\Api\Providers\ApiServiceProvider;
306
+ // ...
307
+ public function register()
308
+ {
309
+ $this->app->register(ApiServiceProvider::class);
310
+ }
311
+ ```
312
+
27
313
## Getting Started
28
314
This project ships with the [ Lucid Console] ( https://github.com/lucid-architecture/laravel-console ) which provides an interactive
29
315
user interface and a command line interface that are useful for scaffolding and exploring Services, Features and Jobs.
@@ -169,3 +455,17 @@ Route::get('/users', 'UserController@get');
169
455
```
170
456
171
457
Now if you visit ` /api/users ` you should see the JSON structure.
458
+
459
+ ## Microservices
460
+ If you have been hearing about microservices lately, and wondering how that works and would like plan your next project
461
+ based on microservices, or build your application armed and ready for the shift when it occurs, Lucid is your best bet.
462
+ It has been designed with scale at the core and the microservice transition in mind, it is no coincidence that the
463
+ different parts of the application that will (most probably) later on become the different services with the microservice
464
+ architecture, are called ** Service** . However, [ it is recommended] ( http://martinfowler.com/bliki/MonolithFirst.html )
465
+ that only when your monolith application grow so large that it becomes crucial to use microservices for the sake of
466
+ the progression and maintenance of the project, to do the shift; because once you've built your application using Lucid,
467
+ the transition to a microservice architecture will be logically simpler to plan and physically straight-forward
468
+ to implement. There is a [ microservice counterpart] ( https://github.com/lucid-architecture/laravel-microservice )
469
+ to Lucid that you can check out [ here] ( https://github.com/lucid-architecture/laravel-microservice ) .
470
+
471
+ With more on the means of transitioning from a monolith to a microservice
0 commit comments