Skip to content
This repository was archived by the owner on Dec 11, 2020. It is now read-only.

Commit 66fc9f1

Browse files
committed
update readme
added appendix, introduction, components, data, foundation and microservices.
1 parent e4d4ece commit 66fc9f1

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed

readme.md

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
Read about the [Lucid Architecture Concept](https://medium.com/vine-lab/the-lucid-architecture-concept-ad8e9ed0258f).
44

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+
516
## Installation
617
### 5.3
718
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
2435
composer create-project lucid-arch/laravel=5.1.x my-project-5.1
2536
```
2637

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+
27313
## Getting Started
28314
This project ships with the [Lucid Console](https://github.com/lucid-architecture/laravel-console) which provides an interactive
29315
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');
169455
```
170456

171457
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

Comments
 (0)