Slide 1

Slide 1 text

Transformando código spaghetti em código... L

Slide 2

Slide 2 text

Talysson @talyssonoc talyssonoc.github.io Web dev / Codeminer42

Slide 3

Slide 3 text

O que é código spaghetti?

Slide 4

Slide 4 text

Banco de dados Serviço externo Regras de negócio Casos de uso Tratamento de erros Emails HTTP Validações Serialização Filas

Slide 5

Slide 5 text

Banco de dados Sistema de filas Tratamento de erros Regras de negócio Casos de uso Emails HTTP Validações Serialização Serviço externo Código spaghetti

Slide 6

Slide 6 text

Banco de dados Sistema de filas Regr as de negó cio Casos de uso Emails HTTP Serial ização Banco de dados Sistema de filas Regr as de negó cio Casos de uso Emai ls HTTP Serialização Serviço externo Banco de dados Regr as de negó cio Casos de uso Trata mento de erros HTTP Validações Serial ização Serv iço exte rno Banco de dados Sistema de filas Casos de uso HTTP Serv iço exte rno Código spaghetti dividido em “services”

Slide 7

Slide 7 text

Como identificar? ● Difícil de encontrar coisas ● Difícil de testar ● Nomes de classes que indicam “fazedores” (-er, -or) ● Acoplamento entre as unidades ● Adicionar features implica em mexer em vários lugares ● Difícil de modificar

Slide 8

Slide 8 text

…if you’re afraid to change something it is clearly poorly designed. - Martin Fowler “

Slide 9

Slide 9 text

Código spaghetti não tem estrutura fácil de mudar

Slide 10

Slide 10 text

Código spaghetti não segue uma boa arquitetura

Slide 11

Slide 11 text

O que é arquitetura?

Slide 12

Slide 12 text

O que não é arquitetura? ● Não é conjunto de tecnologias ● Não é conjunto de bibliotecas e framework usado ● Não é onde sua aplicação roda (web, desktop, CLI) O que é arquitetura? ● Separar responsabilidades ● Separar conceitos ● Deixar explícito o que sua aplicação realmente faz ● Priorizar a parte mais importante da sua aplicação

Slide 13

Slide 13 text

Responsabilidades

Slide 14

Slide 14 text

const { User, Address } = require('../models); const UserWelcomeMailer = require('../mailers'); app.post('/users', async (req, res) => { const userData = req.body.user; if(!userData.name) { return res.status(422).json({ error: 'Name is required' }); } const { address } = userData; if(!address || !address.street || !address.number || !address.zipCode) { return res.send(422).json({ error: 'Address is required' }); } try { const user = await User.create(userData); user.address = await Address.create(address); res.status(201).json({ user }); UserWelcomeMailer.send({ user }); } catch(error) { res.send(400).json({ error }); } });

Slide 15

Slide 15 text

Receber request e pegar os dados Validar dados Retornar erro com status HTTP de acordo com o erro Persistir usuário no banco de dados Retornar usuário criado com status HTTP 201 Qual a parte mais importante deste fluxo? Enviar email

Slide 16

Slide 16 text

Pegar dados da da request Criar usuário Montar resposta

Slide 17

Slide 17 text

Pegar dados da da request Criar usuário Caso de uso da aplicação Montar resposta

Slide 18

Slide 18 text

Pegar dados da da request Criar usuário Montar resposta

Slide 19

Slide 19 text

Pegar dados da da request Criar usuário Montar resposta

Slide 20

Slide 20 text

Pegar dados da da request Criar usuário Montar resposta Ponto de entrada

Slide 21

Slide 21 text

Pegar dados da da request Criar usuário Montar resposta Ponto de entrada Caso de uso

Slide 22

Slide 22 text

Pegar dados da da request Criar usuário Montar resposta Ponto de entrada Aplicação

Slide 23

Slide 23 text

Pegar dados da da request Criar usuário Montar resposta Ponto de entrada Aplicação

Slide 24

Slide 24 text

Aplicação Pegar dados da da request Criar usuário Ponto de entrada Montar resposta Aplicar regras aos dados

Slide 25

Slide 25 text

Aplicação Pegar dados da da request Criar usuário Ponto de entrada Montar resposta Aplicar regras aos dados Domínio

Slide 26

Slide 26 text

Aplicação Pegar dados da da request Criar usuário Ponto de entrada Montar resposta Aplicar regras aos dados Domínio

Slide 27

Slide 27 text

Aplicação Pegar dados da da request Criar usuário Ponto de entrada Montar resposta Aplicar regras aos dados Persistir usuário Enviar email Domínio

Slide 28

Slide 28 text

Aplicação Pegar dados da da request Criar usuário Ponto de entrada Montar resposta Aplicar regras aos dados Persistir usuário Enviar email Domínio Exterior

Slide 29

Slide 29 text

Aplicação Pegar dados da da request Criar usuário Ponto de entrada Montar resposta Aplicar regras aos dados Persistir usuário Enviar email Infraestrutura Domínio

Slide 30

Slide 30 text

Aplicação Pegar dados da da request Criar usuário Ponto de entrada Montar resposta Aplicar regras aos dados Persistir usuário Enviar email Infraestrutura Domínio

Slide 31

Slide 31 text

Ponto de entrada Infraestrutura Domínio Aplicação Camadas

Slide 32

Slide 32 text

Banco de dados Sistema de filas Tratamento de erros Regras de negócio Casos de uso Emails HTTP Validações Serialização Serviço externo Antes

Slide 33

Slide 33 text

Casos de uso Ponto de entrada Domínio Infraestrutura Depois

Slide 34

Slide 34 text

≋ Camadas ≋ Por ordem de importância

Slide 35

Slide 35 text

Domínio ● Entidades e regras de negócio ● Camada mais isolada e importante ● Usada pela camada de aplicação para definir casos de uso ● Ex.: User, Group, JoinGroupRule

Slide 36

Slide 36 text

class User { constructor({ name, age }) { this.name = name; this.age = age; } isValid() { return Boolean(this.name) && this.isOfAge(); } isOfAge() { return this.age >= 18; } }

Slide 37

Slide 37 text

Aplicação ● Casos de uso e funcionalidades ● Realiza a interação entre unidades de domínio ● Casos de uso tem mais de dois possíveis resultados ● Também é um adapter para a camada de infraestrutura ● Ex.: JoinGroup, CreateUser, EmailService

Slide 38

Slide 38 text

class CreateUser extends EventEmitter { constructor({ userRepository }) { this.userRepository = userRepository; } async execute(userData) { const user = new User(userData); if(!user.isValid()) { return this.emit('invalidUser'); } try { const newUser = await this.userRepository.add(user); this.emit('success', newUser); } catch(error) { this.emit('error', error); } } }

Slide 39

Slide 39 text

const createUser = async (userData, { onSuccess, onError }) => { const user = new User(userData); if(!user.isValid()) { return onError(new Error('Invalid user')); } try { const newUser = await UserRepository.add(user); onSuccess(newUser); } catch(error) { onError(error); } };

Slide 40

Slide 40 text

● Comunicação direta com o exterior do software ● A mais baixa das camadas ● Tratada como detalhe de implementação ● Ex.: UserRepository/UserStorage, MailchimpService, PayPalService Infraestrutura

Slide 41

Slide 41 text

class UserRepository { async add(user) { const userDbData = UserMapper.toDatabase(user); const newUser = await UsersTable.insert(userDbData); return UserMapper.toEntity(newUser); } }

Slide 42

Slide 42 text

const UserRepository = { async add(userData) { const response = await fetch('http://api.example.com/users', { method: 'POST', body: JSON.stringify(userData) }); const newUserData = await response.json(); const newUser = new User(newUser); return newUser; } };

Slide 43

Slide 43 text

Pontos de Entrada ● Menos importante das camadas ● Sem nenhum tipo de regra de negócio ● Pega dados da entrada e passa para a camada de aplicação ● Ex.: controllers (HTTP), workers (filas), CLI, actions (Redux)

Slide 44

Slide 44 text

const UsersController = { create(req, res, next) { const createUser = new CreateUser(); createUser .on('success', (user) => { res.status(201).json(userSerializer.serialize(user)); }) .on('invalidUser', (error) => { res.status(400).json({ type: 'ValidationError', details: error.details }); }) .on('error', next); createUser.execute(req.body); } };

Slide 45

Slide 45 text

const createUserAction = (userData) => (dispatch) => { dispatch(startCreateUser()); createUser(userData, { onSuccess: (newUser) => dispatch(successCreateUser(newUser)), onError: (error) => dispatch(failCreateUser(error)); }); };

Slide 46

Slide 46 text

Conectando camadas ● Comunicação direta causa acoplamento ● Injeção de dependência ● Inversão de controle (IoC) ● Não deve fazer com que se crie mais acoplamento ● Soluções

Slide 47

Slide 47 text

const UsersController = { create(req, res, next) { const createUser = new CreateUser(); createUser .on('success', (user) => { res.status(201).json(userSerializer.serialize(user)); }) .on('invalidUser', (error) => { res.status(400).json({ type: 'ValidationError', details: error.details }); }) .on('error', next); createUser.execute(req.body); } }; Acoplamento

Slide 48

Slide 48 text

class CreateUser extends EventEmitter { constructor({ userRepository }) { this.userRepository = userRepository; } async execute(userData) { const user = new User(userData); if(!user.isValid()) { return this.emit('invalidUser'); } try { const newUser = await this.userRepository.add(user); this.emit('success', newUser); } catch(error) { this.emit('error', error); } } } Injeção de dependência

Slide 49

Slide 49 text

Awilix NodeJS ● Injeção de dependência de maneira isolada ● Não causa mais acoplamento ● Não atrapalha testar ● Fácil de usar ● Possui adapters para Express, Koa e Hapi ● npmjs.com/package/awilix

Slide 50

Slide 50 text

● Injeção de dependência diretamente nas actions ● Não modifica o fluxo do Redux ● Não adiciona complexidade ● https://github.com/reduxjs /redux-thunk#injecting-a-c ustom-argument withExtraArgument Redux + Redux-Thunk

Slide 51

Slide 51 text

Também vale para CSS! ITCSS

Slide 52

Slide 52 text

Sem exageros! ● Separação demais no código causa complexidade ● A aplicação não pode ficar difícil de entender ● Encontre o equilíbrio ● Não precisa aplicar tudo

Slide 53

Slide 53 text

Show me the code!

Slide 54

Slide 54 text

Node API Boilerplate https://goo.gl/tgQH8v

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

Lasagna framework ● Convenção sobre configuração ● Ferramentas já estabelecidas da comunidade ● Baixa curva de aprendizagem ● Arquitetura escalável ● Segue ideias do 12 Factor App ● Pronto para sair produzindo ● @talyssonoc / #euquerolasagna ● [email protected]

Slide 57

Slide 57 text

Links ● Bob Martin - Architecture the lost years: https://youtu.be/WpkDN78P884 ● Rebecca Wirfs-Brock - Why We Need Architects (and Architecture) on Agile Projects: https://youtu.be/Oyt4Ru7Xzq0 ● Mark Seemann - Functional architecture - The pits of success: https://youtu.be/US8QG9I1XW0 ● Bob Martin - The Clean Architecture: https://goo.gl/2N92AV ● Domain-Driven Design Books - https://goo.gl/27bjVK ● Eu mesmo - NodeJS and Good Practices: https://goo.gl/YPNpoh ● Eu mesmo 2 - NodeJS e boas práticas: https://goo.gl/HNn9Xm ● Jeff Hansen - DI in NodeJS: https://goo.gl/jasFHm ● Iago Dahlem - How to Organize your Styles with ITCSS: https://goo.gl/YopDzz

Slide 58

Slide 58 text

Obrigado! @talyssonoc talyssonoc.github.io