title | revealOptions | |
---|---|---|
MVC ➡ ADR |
|
Woody Gilk
@shadowhand
#mwphp17
Note: Who has used MVC? // Heard of ADR? // Used ADR?
Note: possibly most widely used design pattern in web
It is a software design pattern.
It separates the mental model
from the computer model.
Note: user ui != computer ui // ux = how elegant the transformation is
Note: <3
It was made for GUI applications.
Note: history in a minute // predates the web
Later adopted for web applications.
Note: desktop is different than the web // on to history
Note: abbreviated
Trygve Reenskaug invents MVC
while visiting Xerox PARC.
Note: Try-guhv Reens-kaug // Palo Alto Research Center
MVC is part of Smalltalk-80.
Published as a design pattern in JOT.
Note: Journal of Object Technology
Apple WebObjects adopts MVC.
WebObjects is rewritten in Java.
Note: first usage of MVC for web
Spring framework for Java is released.
Note: first open source usage of web MVC
Mojavi framework for PHP is released.
Note: first PHP usage
PRADO is started in January.
Rails first release in July.
Agavi forked from Mojavi in May.
Django released in July.
Cake announced in August.
Symfony announced in October.
Note: django started in 2003 privately // the year of web MVC for php // simultaneous scientific discovery // PHP only from here on
CodeIgniter, Zend Framework, SilverStripe
Kohana
Note: more on Kohana later
Lithium
PHP-FIG begins to organize.
Laravel, Fuel, Silex, TYPO3
Phalcon
et al
Note: most PHP frameworks now use MVC
Paul M Jones publishes ADR
as an alternative to MVC.
Note: most not applicable for PHP
Data flow review.
Note: i am bad at UML // i'll pay you to make good diagrams // questions?
Note: 1990s at Taligent // testing // view and model interaction // more direct domain access // Nette Framework, PHP, 2006
Note: first published July 2000 in Javaworld // multiple triads to process a request // used by Kohana in 2009 // later used by Fuel in 2010
Note: 2005 by John Gossman @ MS // WPF: Windows Presentation Framework // Silverlight // Knockout JS and ZK (Java) // uses data bindings // rarely seen in PHP
Note: history over
Really popular.
Note: majority of frameworks use it // excluding CMS space // popularity is familiarity
Good starting point.
Note: organizing code = win // any structure is better than none // separates mental/computer model
It works.
Mostly.
Note: only one design pattern // is pretty high level // remember the gui?
MVC, as originally envisaged, is not an end-to-end monolithic application pattern, it's a UI pattern.
Web applications are fundamentally
different than desktop applications.
Note: talk about server side apps for a minute // the great mismatch
Note: life cycle fundamental difference // request, response
Request ↝ Response
Note: app flow is constant // state is maintained all the time // http is stateless
HTTP is stateless.
Note: if something is nowhere, it can be anywhere
How can a desktop pattern work for the web?
By meddling with the pattern.
😱
Model was meant to be a domain gateway.
Note: gateway/coordinator // problem domain // application model (mvp reference) // observers
View was meant to contain some logic.
Note: more purpose than just a template // aware of the model data (read only) // immutability can help here
Controller was meant to handle input and state.
Note: domain logic does not go here // coordinates model output and view input // commands
Every screen was a multitude of MVC triads.
Note: HMVC // widgets // dissonance with original intent
Note: not universal problems
Controller has become synonymous with resource.
Note: multiple routes go to the same place // users controller // products controller // blog controller
'GET /users' => 'UsersController::all',
'GET /users/{id}' => 'UsersController::one',
'POST /users/{id}' => 'UsersController::update',
'POST /users/{id}/follow' => 'UsersController::follow',
Note: how many have seen this? // maybe even less routes? // one route per controller!
Controller has become a "God Object".
Note: anti-pattern // separation of concerns // multiple HTTP methods = code smell // input and state
Controllers were meant to be thin.
Few dependencies, not much logic.
Note: put domain where it belongs // ease of refactoring // testability
Original purpose of controllers...
Note: state is user state, not application state // input and state come from? (request) // commands for model and view // keep thinking about this
Model has become storage.
Note: applications are more than storage // where are application rules? // where is domain code?
Models look like a single type.
Note: models are not a type, but a generalization // unless domain is storage, do lots of things // read, write, services, externals
Models were meant to be thick.
Many dependencies, not big classes.
Note: gateway for domain // lots of coordination // authorization // application rules // storage separate // command bus // cqrs
Original purpose of models ...
Note: receives commands // produces output // coordinates domain, does not replace it
View has become a template.
Note: where do headers go? // where is content type? // API vs HTML formatting // multi format of web
Views were meant to be dumb.
Very little context, not free of logic. Note: formatting of data // specific to type of output // pacman chomp chomp
Original purpose of views ...
Note: receives data // prepares for display
... the more I learn about MVC, the more I think it has little to nothing to do with server-side web applications.
Note: is all lost? // is there something better?
Note: created by Paul M Jones in 2014 // replacement for MVC on the web
Action ↔ Controller
Domain ↔ Model
Responder ↔ View
Note: web specific // note order
- does not manipulate Responder
- is always singular
'GET /users/{id}' => User\UserProfile::class,
'POST /users/{id}' => User\UserUpdate::class,
- does not interact with Responder
- might not be a concrete component
- does not interact with Domain
- corresponds with Action
- generates a complete response
Note: complete contains headers // output format is contained
Flow comparison?
- clean break from existing terms
- better separation of concerns
- domain is a first class citizen
Note: possibly easier to reason about // web specific, easier to adapt // simplified data flow
Is ADR here to save us all?
Note: my thinking has evolved
Time to explore code.
Note: not equip
Goal is really simple invocation.
$response = $action($request);
Note: determine the action from routing // construct the action // generate response
Basic user login.
Note: PSR-7 interfaces
public function __invoke(Request $request): Response
{
// No domain interaction required.
return $this->responder->response();
}
Note: action for showing login
public function response(): Response
{
$content = $this->templates->render('login');
return $this->htmlResponse($content);
}
private function htmlResponse(string $content): Response
{
// ...
}
Note: responder for showing login
public function response(): Response
{
// ...
}
private function htmlResponse(string $content): Response
{
$body = $this->streamFactory->createStream($content);
return $this->responseFactory->createResponse()
->withHeader('Content-Type', 'text/html');
->withBody($body);
}
Note: responder for showing login
- Action has no input or state
- Responder generates a complete response
And now with more input.
Performing a login.
Note: command bus usage
public function __invoke(Request $request): Response
{
$post = $request->getParsedBody();
$command = LoginCommand::byUsername(
$post['username'],
$post['password']
);
return $this->attempt($command);
}
private function attempt(LoginCommand $command): Response
{
// ...
}
Note: post login action
private function attempt(LoginCommand $command): Response
{
try {
$user = $this->bus->handle($command);
} catch (LoginFailedException $e) {
return $this->responder->failed($command, $e);
}
return $this->success($user);
}
private function success(User $user): Response
{
// ...
}
Note: post login action // domain exception
private function attempt(LoginCommand $command): Response
{
// ...
}
private function success(User $user): Response
{
$this->session->set('userid', $user->id);
$this->session->set('username', $user->username);
return $this->responder->redirect('/user/profile');
}
Note: post login action
public function failed(
LoginCommand $command,
LoginFailedException $e
): Response
{
$data = [
'input' => $command->toArray(),
'errors' => $e->getErrors(),
]
$content = $this->templates->render('login_failed', $data);
return $this->htmlResponse($content);
}
Note: post login responder // html response is reused
public function failed(/* ... */): Response
{
// ...
}
public function redirect(string $target): Response
{
return $this->responseFactory->createResponse(302)
->withHeader('Location', $target);
}
Note: post login responder
- Action uses input from the request
- Domain accessed by command bus
- Responder handles two scenarios:
- failure, showing errors
- success, redirecting
Note: or "Does this work with MVC?" // my thinking has changed
Could the same thing be accomplished with MVC?
🤔
What about all that life cycle stuff?
MVC is a design pattern.
Note: not an architecture pattern
The point here is that the domain should be utterly unaware of what presentations may be used with it.
Note: Separated Presentation
Use the pattern correctly.
Note: remember what mvc is and isn't // adr isn't actually radical
Don't let MVC dictate your domain architecture.
Keep domain code out of presentation code.
Use what works for you.
Everything will be okay.
😎
github.com/shadowhand/mvc-example
- I live in Grand Marais, Minnesota.
- I like code, food, bikes, and beer.
- I work remotely for RoundingWell.
- Find me everywhere as shadowhand.
More at talks.shadowhand.me.
Go forth.
Make stuff.
Be awesome.
fin.
Note: JoindIn link