Hello John,
Glad to hear you've decided to jump on the Lucid arch's bandwagon. I hope the below answers your questions appropriately.
1- Do I return views from the Job or the Feature or the Controller?
The chain should always go as Controller -> Feature -> Job. Which is the expected structure by anyone reading a Lucid project, so if you were to return views or anything else from an intermediate step like Controller or Feature, it would break the chain and would end up unpredictable.
2- Where is the request validated?
This is up to you to decide. Preferably in Lucid, everything to go in a Job. So at the beginning of the feature you'd have a job to validate the incoming request:
$this->run(ValidateUserRegistrationJob::class, ['input' => $request->input()]);
- Then that job would call a validator that it injects
handle(UserValidator $validator) {
$validator->validatorForRegistration();
}
-
The
UserValidatorclass would live within theUserdomain (or any domain relevant to what's being validated) -
It would extend
Lucid\Foundation\Validator;as follows:
use Lucid\Foundation\Validator;
class UserValidator extends Validator
{
protected $rules = [
'name' => 'required',
];
}
- With that, simply calling
$validator->validate($input)would do the validation and if it doesn't pass an exception is thrown which then will be handled by the Exception Handler of the app
3- Do I dissect the request into variables or pass the request along to the Feature/Job
- The controller passes the request as is to the Feature (this happens automatically using injection at the Feature's
handlemethod so no need to manually pass the request) - The Job specifies what it needs to work, which is specific variables/values from the request which are then passed by the Feature to the Job accordingly
4- I have many joins on most of my front-end requests so in the past I've just used raw queries and hydrated the result into the model. I realize this isn't the best practice and I'd like to use Repositories in the Data folder, but I'm not sure how that would look in my case.
I am assuming that this is a structure question based on "I'm not sure how that would look in my case".
One generic way is to have ../Data/Repositories/UserRepository.php hosting your user queries. If you were to be more specific and account for changes in storage later on, you'd specify what these repositories work with. i.e. .../Data/Repositories/MySQL/UserRepository.php and then use IoC to autoload accordingly.
Code examples feedback
First snippet
Perfectly correct
Second snippet
1-
$lang = $this->run(GetSessionLangJob;::class);
Good example of using jobs
2-
return $this->run(GetArticlesJob::class, compact('request','lang'));
-
A job should do one thing at a time, here it is doing at least two.
GetArticlesJobshould return a list of articles, i.e. aCollection. This ensures reusability, i.e. if you were to call theGetArticlesJobbut would rather return the result as JSON instead of a view -
GetArticlesJobshould not receive therequestbut rather what it needs exactly from the request. This ensures code readability and predictability. When I read the Feature I need to understand all the requirements of each step, besides the sequence and the end result -
The Feature should be explicit about what it returns. To accomplish that, you need to have a job to return a view from the given data and for that you can use the built-in
RespondWithViewJobwhich is usually shipped with each Lucid installation atApp\Domains\Http\Jobs\ RespondWithViewJob -
GetArticlesJobis doing too much work itself, I suggest you take a look at (Operations)[https://tech.vinelab.com/introducing-operations-the-lucid-architecture-bad45f259a1d] and think of ways to 1) dissect it into pieces 2) delegate those pieces to Jobs. Maybe follow the Builder design pattern to build queries along with Repositories to facilitate the process. Below are a few examples: -
Deterministic conditions can be delegated to jobs
if($this->lang == env('APP_LOCALE_LANG'){
$query .= ", A.title as _display_name";
}else{
$query .= ", A.title_" . $this->lang . " as _display_name",
}
pass the env('APP_LOCALE_LANG') as a param to the job
-
Dissect the query to pieces where each piece is built by a Job within the Operation
-
The following to be delegated to a Job that figures out pagination since it doesn't fall under the responsibility of getting the articles from the database, though called within this Operation
$next_page_url = "";
if(count($articles) == $limit){
// probably can try loading another page
$next_page = $page + 1;
$next_page_url = $request->fullUrlWithQuery(['page' => $next_page]);
}else{
$next_page_url = "";
}
- This one requires a bit of refactoring
if($this->request->ajax()){
return [
'articles' => view('articles.ajax.show')->with(compact('articles'))->render(),
'next_page_url' => $next_page_url,
'article_count' => count($articles),
'total_article_count' => $total_article_count
];
}
I suggest this to go in its own Operation, and the check for an Ajax request happens at the level of the Feature through a Job and calls a different Operation. Both Ajax and NonAjax operations would call the same Jobs (hence reusability of Jobs). The reason for this separation is the different concerns they tackle which isn't predictable by simply reading the Feature, especially that Ajax returns a different subset of the data since if it weren't Ajax you'd continue with the query.
- Return the values expected
compact('articles','total_article_count','next_page_url','root_categories')
And it would be even better if this were a proprietary object instead of a loose array with keys, which is also an unpredictable response structure to know what's to be expected from whoever calls the Operation. i.e. ArticlesList object with methods to return articles(), rootCategories() etc... And if this structure repeats elsewhere in the code, it would be even better to have an interface that enforces these methods to be in the implementing class.
- Eventually in the Feature you'd pass the received Object to the
RespondWithViewJob
Please continue the conversation if you need clarification regarding the above.
Cheers.