37signals Dev — Vanilla Rails is plenty を読んだ ServiceじゃなくてMix-inで良いのかも知れない
自身のフレームワーク経験
- 昔のJava FW. Jakarta Struts cFramework Teeda(Seasar Project)
- CakePHP 2.x
- Symfony 3.x
- Laravel 5.x - 8.x
- CakePHPでModelがFATになるのが辛かったので、Laravelを使用した開発では無条件にControllerとModelの間にService層を追加していた
感想
色々な問題が解決するので、Mix-inを試してみようと思いました
命名問題
recording.incinerate
Recording::Incineration.new(recording).run
Recording::IncinerationService.execute(recording)it feels more natural, like plain English. It feels more Ruby.
{DomainName}Service.execute
だと、使う側は仕様が分かり辛い、作る側はServiceの単位や名称を考えるのが辛い問題があるので、Mix-inの圧勝
正直、Service層を追加してから毎回悩んでます
Anemic Domain Model問題
Don’t lean too heavily toward modeling a domain concept as a Service. Do so only if the circumstances fit. If we aren’t careful, we might start to treat Services as our modeling “silver bullet.” Using Services overzealously will usually result in the negative consequences of creating an Anemic Domain Model, where all the domain logic resides in Services rather than mostly spread across Entities and Value Objects
Modelの属性しか操作していないServiceのfunctionがあったら、適宜Modelに移動している。Serviceが無ければModelに実装するので、この作業は不要になる
移譲問題
Tons of boilerplate code because many of these application-level elements simply delegate the operation to some domain entity. Remember that the application layer should not contain business rules. It just coordinates and delegates work to domain objects in the layer below.
マスタメンテ系の単純な画面だと、ServiceがModelに移譲するだけになるので、Service層を使わない判断をしている。これも、Serviceが無ければModelに実装するので、この作業は不要になる
LaravelのRoute Model Bindingを使用すると、LaravelがControllerとModelを紐付けするのでServiceを強制すると冗長的なコードになってしまう
- Service無し
<?php use App\Models\User; // Route definition... Route::get('/users/{user}', [UserController::class, 'show']); // Controller method definition... public function show(User $user) { return view('user.profile', ['user' => $user]); }
- Service有り
<?php public function show(int $userId) { $service = app(UserService::class); $user = $service->findBy($userId); return view('user.profile', ['user' => $user]); }