lucidarch / laravel

[DEPRECATED] See https://github.com/lucidarch/lucid

Home Page:https://lucidarch.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Should feature depend on a Request object?

lezhnev74 opened this issue · comments

Hi Abed! Just watched your great talk on LaraconEU and one thing keeps jumping in my head.

Should Feature really depend on Http Request?
I mean there might be different cases where we want to call the feature (for example from console command). I always thought of Controllers as adapters between HTTP world and internal business logic.

What is your opinion on that?

I'd say there can be some decoupling like this:

class SomeFeature() extends Feature {
    public function handle($input_data) {}
}

...

class SomeController extends Controller {
    public function some(Request $request) {
        // pass request payload as an input to a feature
        $data = $this->serve(SomeFeature::class, $request->all());
        return Response::json($data);
    }
}

@lezhnev74 Glad you enjoyed the talk!

What you said about Features not depending on Request makes total sense, which is why they don't necessarily have to do with requests. If you do not inject the Request in your Feature class it won't need it. As for calling a Feature from the a console command or any other place is totally possible.

You have to simply use Lucid\Foundation\ServesFeaturesTrait in the class that you'd like to serve a Feature from. In certain cases that Feature might also be exposed through both HTTP and Console, in that case there are two things you can do:

1- What you've done in your controller, pass the input of the request to the Feature without having it depend on the request. If the Request was not specified in the Feature's handle method then it won't be injected. What you have to do though, is one of the following:

$this->serve(new SomeFeature($request->all()));

OR

$this->serve(SomeFeature::class, ['input' => $request->all()]);

And with both cases, in your Feature's constructor:

class SomeFeature() extends Feature
{
    public function __construct($input)
    {
        $this->input = $input;
    }

    public function handle()
    {
        // access input with $this->input
    }
}

One drawback I see with this approach is that you lose the wits of the $request->input() where you can specify defaults and be fail-safe compared to accessing the input as an array $this->input['something'].

2- Use Request::replace() in the command in case the Feature requires the Request object to be present:

public function handle(Request $request)
{
    $request->replace(array('name' => $this->argument('name'), 'email' => $this->argument('email')));
    ...
}
Side Note

On a side note, it would be cleaner to return what's being returned from the Feature, and have it construct the response with a Job (i.e. RespondWithJsonJob from the Http domain), you can change the structure of the response in there to fit your needs and have all the JSON features use it.

public function some(Request $request)
{
        // pass request payload as an input to a feature
        return $this->serve(SomeFeature::class, $request->all());
}

@harris21 can also shed some light on that matter for he's done that kind of work in one of his projects.

Thanks for the in-depth reply!

I remember you were saying that every service should have its own copy of a feature. This way things stay more maintainable. I understand that.

Regarding my question, I believe there should be two separate Features for "web" and "console" services (with the same name if needed). Does that look like a solution to you?

p.s. I participated in many growing over time projects and saw the mess of unstructured (utility\helper classes everywhere) architecture. I was thinking over this problem for a long time and your Lucid approach looks like a way of solving design and maintainability problems.
Will definitely dive into it.

p.p.s. Are you planning to make a website to keep the discussion about this Lucid approach - it seems there can be many things to discuss and many developers can benefit from that.

@lezhnev74 having duplicate features to serve different entry points isn't the solution for your case, in fact this will violate the Lucid principle of reusability, since features don't know or care actually who will be calling them, they can serve these entry points (web, console or messaging queue).

I've implemented a feature which deals with messaging queue and it definitely can serve a controller or console commands:

class StoreDataFeature extends Feature
{
    private $msg;

    public function __construct($msg)
    {
        $this->msg = $msg;
    }

    public function handle()
    {
        $msg = $this->run(new DecodeMessageJob($msg));

        $this->run(new ValidateInputJob($msg));

        return $this->run(StoreDataJob::class,
            [
                'id'        =>  $msg['talent_id'],
                'country'   =>  $msg['country'],
                'age'       =>  $msg['age'],
                'date'      =>  $msg['fetched_at']
            ]
        );
    }
} 

As you can see in the above feature (disregarding the implementation of the handle() method), this same feature can be called from a controller or from console.

@Aliissa Yeah, your solution does not depend on Request object and rather on some internal message which can be decoded with a job DecodeMessageJob - this is a way of implementation.

My initial concern was about Feature needs to know about HttpRequest object. As Abed explained he liked the power of built-in helpers that comes with Request object. In your case, Feature depends on some internal message format.

Both ways work.

Hey @lezhnev74, regarding calling a feature using a different way other than the HTTP - conventional one, please take a look at this blog post.
I hope this can be helpful and makes things more clear for you.

@lezhnev74 regarding your point "p.p.s. Are you planning to make a website to keep the discussion about this Lucid approach - it seems there can be many things to discuss and many developers can benefit from that."

Here it is 😄 https://lucid-slack.herokuapp.com

Please feel free to invite whoever you know might be interested.