Milko Kosturkov
An experienced developer specializing in the web.
SMS and Voice services
MMO Games
local positioning systems
TV productions
Healthcare
e-commerce
websites
SEO
online video
Help all the teams involved in the production do their job quicker, easier and with better quality...
We didn't really know what we were supposed to create
"Your architectures should tell readers about the system, not about the frameworks you used in your system."
"Uncle" Bob Martin
<?php
use App\Http\Controllers;
class ContestantController extends Controller {
.
.
.
public function advanceToRound(Request $request) {
Gate::authorize('contestant-advancement');
$params = $request->validate([
'contestantId' => 'required|int',
'round' => 'required|in:casting,competition',
'signedContract' => 'required|file',
]);
$contestant = Contestant::findOrFail($params['contestantId']);
if ($contestant->jurryApprovals()->doesntExist()) {
throw new RuntimeException(t('Not approved for advancement!'));
}
$contestant->round = $params['round'];
$contastant->advancedByUserId = Auth::id();
$contestant->save();
$request->file('signedContract')->store('contestants/docs');
return redirect(route('contestant-profile', $params['contenstantId']));
}
.
.
.
}
<?php
namespace MusicIdol\Domain\Contestants;
use Exceptions\ContestantNotApprovedForAdvancement;
class AdvanceToRound {
public function __construct(
private ActorProvider $ap,
private ContestantAuthorizations $auth,
private ContestantsRepository $repo,
private Transaction $tr
) {}
public function __invoke(AdvanceToRoundParams $params): void
{
$this->auth->authorizeContestantAdvancement($this->ap->actor());
$contestant = $this->repo->findById($params->contestantId());
if ($contestant->isNotApproved()) {
throw new ContestantNotApprovedForAdvancement();
}
$contestant->round = $params->round();
$contestant->advancedByUser = $this->ap->actor();
$contestant->signedContract = $params->signedContract();
$tr->persist($contestant);
}
}
<?php
namespace MusicIdol\Domain\Contestants;
class AdvanceToRoundParams {
private int $contestantId;
private string $round;
private File $signedContract;
private function __construct() {}
public function contestantId(): int {
return $this->contestantId;
}
.
.
.
public static function fromArray(array $params): self
{
$validated = Validator::make($params, [
'contestantId' => 'required|int',
'round' => 'required|in:casting,competition',
'signedContract' => 'required|file',
])->validate();
$instance = new self();
$instance->contestantId = $validated['contestantId'];
$instance->round = $validated['round'];
$instance->signedContract = $validated['signedContract'];
return $instance;
}
}
<?php
namespace MusicIdol\Domain\Contestants;
use Excepttions\UnauthorizedToAdvanceUser;
class ContestantAuthorizations {
public function authorizeContestantAdvancement(User $actor): void {
if ($user->role !== User::ROLE_CASTING_TEAM) {
throw new UnauthorizedToAdvanceUser();
}
}
}
<?php
namespace MusicIdol\Domain\Users;
interface ActorProvider {
public function actor(): User;
}
<?php
namespace MusicIdol\Domain\Transaction;
interface Transaction {
public function persist(): void;
}
<?php
namespace MusicIdol\Domain\Contestants;
interface ContestantsRepository {
public function findById(): Contestant;
}
<?php
class AdvanceToRoundController extends Controller {
public function __contructor(
private AdvanceToRound $action;
) {}
public function __invoke(Request $request) {
$params = AdvanceToRoundParams::fromArray($request->post());
($this->action)($params);
return redirect(route('contestant-profile', $request->post('contenstantId')));
}
}
[
'match' => [
'method' => 'GET',
'path' => '/contestants/{id}/notes/new'
],
'service' => [
'className' => MusicIdol\ContestantsManagement\AdvanceToRound::class,
'parametersFactory' => fn (R $r) => AdvanceToRoundParams::fromArray($r->post()),
'responder' => fn (R $r, $result) => redirect(
route('contestant-profile', $r->post('contestantId'))
);
]
],
...
[
'match' => [
'command' => 'advanceToRound'
],
'service' => [
'className' => MusicIdol\ContestantsManagement\AdvanceToRound::class,
'parametersFactory' => fn (Command $c) =>
AdvanceToRoundParams::fromArray($c->arguments()),
'responder' => fn (Command $c, $result) => $c->info('Done');
]
],
...
Domain Driven Design: Tackling Complexity in the Heart of Software
Eric Evans
Clean Architecture Talk
https://www.youtube.com/watch?v=Nsjsiz2A9mg
"Uncle" Bob Martin
Action Domain Responder
https://github.com/pmjones/adr
Paul Jones
Milko Kosturkov
@mkosturkov
linkedin.com/in/milko-kosturkov
mailto: mkosturkov@gmail.com
These slides:
https://slides.com/milkokosturkov/minimize-the-framework-and-allow-yourself-some-ddd-v2
By Milko Kosturkov
By now we've all heard what Domain Driven Design is about. Some of us have actually tried it. When we did, we stumbled upon the fact that the frameworks we use do not help us a lot with shaping our code in a domain driven manner. Actually, they were in the way. DDD and the generic MVC frameworks we use are an oxymoron. Still, we don't want to loose all the sweet tools that help us deal with the HTTP and the Console. In this talk I'll tell you how we changed our view perspective on input/output/request/response, pushed the framework in the infrastructure corner and cleaned ourselves some space for sweet DDD.