[comment]: # "markdown: { smartypants: true }" ## MVC is Dead. (Long Live MVC!)
### Useful Links
Slides:
https://beyondcodebootcamp.github.io/
Video:
https://youtu.be/bO7nynlX8a0
AJ ONeal
[@\_beyondcode](https://twitter.com/@_beyondcode)
[youtube.com/coolaj86](https://youtube.com/coolaj86)
Dangerous Wrong Thinker Equal Opportunity Offender Technophobic Technologist Extraordinairé
Utah Node.js Utah Rust Twitch.tv/coolaj86
## MVC is Dead. (Long Live MVC!)
## What is MVC?
- MVC
- MVC - MVVM
- MVC - MVVM - MVP
A _terrible_ name.
A _terrible_ name. - Model .
A _terrible_ name. - Model . - **Storage** (DB)
A _terrible_ name. - Model . - **Storage** (DB) - `find`, `getBy`
A _terrible_ name. - Model . - **Storage** (DB) - `find`, `getBy` - View
A _terrible_ name. - Model . - **Storage** (DB) - `find`, `getBy` - View - **Presentation** (UI, CLI, API)
A _terrible_ name. - Model . - **Storage** (DB) - `find`, `getBy` - View - **Presentation** (UI, CLI, API) - `render()`, `log()`, 'res.json()'
A _terrible_ name. - Model . - **Storage** (DB) - `find`, `getBy` - View - **Presentation** (UI, CLI, API) - `render()`, `log()`, 'res.json()' - Controller
A _terrible_ name. - Model . - **Storage** (DB) - `find`, `getBy` - View - **Presentation** (UI, CLI, API) - `render()`, `log()`, 'res.json()' - Controller - **Business Logic** (Testable Stuff)
A _terrible_ name. - Model . - **Storage** (DB) - `find`, `getBy` - View - **Presentation** (UI, CLI, API) - `render()`, `log()`, 'res.json()' - Controller - **Business Logic** (Testable Stuff) - `if`, `for`, `throw`
## MVC is Dead
## Models are Dead ❌ We don't _actually_ switch databases
## Views are Dead ❌ No more `{{ ... }}` on Rails!
## Controllers are Dead ❌ Generic "reusability" is a liability
## Categorganization is Dead ```txt foobar5000/ ├──❌ models/ │ ├── gizmos.db │ └── widgets.db ├──❌ views/ │ ├── gizmos.fml │ └── widgets.fml └──❌ controllers/ ├── gizmos.app └── widgets.app ```
> A design pattern is a crutch to use when you have either a > mediocre team, or they have to work with other mediocre > teams. - [TodPunk][todpunk] [todpunk]: https://github.com/todpunk
## Long Live MVC!
MVC is an _Emergent Design_
- ~~Model~~ .
- ~~Model~~ . - Get the right stuff, in the right shape
- ~~Model~~ . - Get the right stuff, in the right shape - ~~View~~
- ~~Model~~ . - Get the right stuff, in the right shape - ~~View~~ - How to interact with the product
- ~~Model~~ . - Get the right stuff, in the right shape - ~~View~~ - How to interact with the product - ~~Controller~~
- ~~Model~~ . - Get the right stuff, in the right shape - ~~View~~ - How to interact with the product - ~~Controller~~ - Business logic, Unique value
- Testability - Throw-away-ability
**Let's Consider Some (backend) Code**
```js [1-9] router.get("/api/gizmos/average-cost", function (req, res) { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; res.result({ average }); }); ```
```js [1,9] router.get("/api/gizmos/average-cost", function (req, res) { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; res.result({ average }); }); ```
```js [2] router.get("/api/gizmos/average-cost", function (req, res) { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; res.result({ average }); }); ```
```js [3-5] router.get("/api/gizmos/average-cost", function (req, res) { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; res.result({ average }); }); ```
```js [6-7] router.get("/api/gizmos/average-cost", function (req, res) { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; res.result({ average }); }); ```
```js [8] router.get("/api/gizmos/average-cost", function (req, res) { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; res.result({ average }); }); ```
Code is for Humans.
Code is for (dumb) Humans.
We break ideas into paragraphs.
Code should read like paragraphs.
```js router.get("/api/gizmos/average-cost", function (req, res) { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; res.result({ average }); }); ```
Code needs comments...
```js router.get("/api/gizmos/average-cost", function (req, res) { // Get stuff in the shape we need it let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); // Do what we need to do let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; // Present the API to the user res.result({ average }); }); ```
Code _shouldn't_ need comments!
Code _shouldn't_ need comments! (comments become out-of-sync)
```js Gizmos.data.getUpsideDownGizmos = async function () { let gizmos = await Db.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); return gizmos; }; ```
```js Gizmos.ctrl.calculateAveragePrice = function (gizmos) { let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; return average; }; ```
```js Gizmos.view.showAverageCost = async function (req, res) { let gizmos = await Db.getUpsideDownGizmos(); let average = calculateAveragePrice(gizmos); res.result({ average }); }; ```
```js router.get( "/api/gizmos/average-cost", Gizmos.view.showAverageCost ); ```
But 4 slides?
But 4 slides? Really??
But 4 slides? Really?? **_FOUR_** files to tab through!?
Well... no.
`./lib/gizmos.js`: ```js Gizmos.data.getUpsideDownGizmos = async function () { // ... }; Gizmos.ctrl.calculateAveragePrice = function (gizmos) { // ... }; Gizmos.view.showAverageCost = function (req, res) { // ... }; ``` `./router.js`: ```js router.get( "/api/gizmos/average-cost", Gizmos.view.showAverageCost ); ```
- Storage - Component (Unit Testable) - Get the right stuff, in the right shape - Business logic, Unique value - View-Controllers - Glue component pieces into presentation - Global Router
- Storage - Component (Unit Testable) - Get the right stuff, in the right shape - Business logic, Unique value - View-Controllers - Glue component pieces into presentation - Global Router - Libraries + Utilities - Generic, reusable stuff
Components + Libs ```txt └── lib/ ├── storage.lib ├── averager.lib └── widgets.lib ``` View-Controllers ``` ├── router/ # API │ ├── widgets.app │ └── serve.app │ ├── webapp/ # Web UI │ └── store.html │ └── bin/ # CLI └── foobar500 ```
Emergent Design
Emergent Design - 🎯 Red
Emergent Design - 🎯 Red - ✅ Green
Emergent Design - 🎯 Red - ✅ Green - ⚙️ Refactor
## Caveat
Circular Component Dependencies
Don't overwrite `exports`:
Don't overwrite `exports`: ```js [1-7] // ✅ let Gizmos = exports; Gizmos.foo = { ... }; // ❌ // exports = Gizmos; ```
Don't overwrite `exports`: ```js [5-7] // ✅ let Gizmos = exports; Gizmos.foo = { ... }; // ❌ // exports = Gizmos; ```
Don't overwrite `exports`: ```js [1-2] // ✅ let Gizmos = exports; Gizmos.foo = { ... }; // ❌ // exports = Gizmos; ```
Don't destructure imports
Don't destructure imports ```js [1-5] // ❌ // let { view: GizmosView } = require('./lib/gizmos.js'); // ✅ let Gizmos = require("./lib/gizmos.js"); ```
Don't destructure imports ```js [1-2] // ❌ // let { view: GizmosView } = require('./lib/gizmos.js'); // ✅ let Gizmos = require("./lib/gizmos.js"); ```
Don't destructure imports ```js [4-5] // ❌ // let { view: GizmosView } = require('./lib/gizmos.js'); // ✅ let Gizmos = require("./lib/gizmos.js"); ```
(and use a directed graph of dependencies)
```txt foobar5000/ └── lib/ ├── gizmo-widgets.lib # lift mutual dependencies ├── gizmos.lib └── widgets.lib ```
**Let's Consider Some (frontend) Code**
```js [1-14] app.route("/#/gizmos/:fn", function (location) { let gizmos = await Api.getGizmos(); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; app.render( './partials/gizmo-average.xjs.html', { gizmos: { cost: average } } ); }); ```
```js Gizmos.data.getUpsideDownGizmos = async function () { let gizmos = await api.get("/api/gizmos"); gizmos = gizmos.filter(function (g) { return g.upsideDown; }); return gizmos; }; ```
```js Gizmos.ctrl.calculateAveragePrice = function (gizmos) { let total = gizmos.reduce(pluckCost, 0); let average = total / gizmos.length; return average; }; ```
```js Gizmos.route.showAverageCost = async function (location) { let gizmos = await Gizmos.data.getUpsideDownGizmos(); let average = Gizmos.ctrl.calculateAveragePrice(gizmos); return { gizmos: { cost: average } }; }; ```
`./router.js`: ```js app.routeAndRender( "/api/gizmos/average-cost", Gizmos.route.showAverageCost ); ``` (could apply to the CLI too)
Like, Sub, & Follow
(if you wannu)
Thanks.
FIN