Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Transformando código spaghetti em código lasagna

Transformando código spaghetti em código lasagna

Saiba como organizar sua aplicação mantendo a separação de conceitos com um arquitetura baseada em camadas como uma lasanha, para evitar código spaghetti.

Transcript

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

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

  3. O que é código spaghetti?

  4. Banco de dados Serviço externo Regras de negócio Casos de

    uso Tratamento de erros Emails HTTP Validações Serialização Filas
  5. 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
  6. 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”
  7. 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
  8. …if you’re afraid to change something it is clearly poorly

    designed. - Martin Fowler “
  9. Código spaghetti não tem estrutura fácil de mudar

  10. Código spaghetti não segue uma boa arquitetura

  11. O que é arquitetura?

  12. 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
  13. Responsabilidades

  14. 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 }); } });
  15. 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
  16. Pegar dados da da request Criar usuário Montar resposta

  17. Pegar dados da da request Criar usuário Caso de uso

    da aplicação Montar resposta
  18. Pegar dados da da request Criar usuário Montar resposta

  19. Pegar dados da da request Criar usuário Montar resposta

  20. Pegar dados da da request Criar usuário Montar resposta Ponto

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

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

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

    de entrada Aplicação
  24. Aplicação Pegar dados da da request Criar usuário Ponto de

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

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

    entrada Montar resposta Aplicar regras aos dados Domínio
  27. 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
  28. 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
  29. 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
  30. 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
  31. Ponto de entrada Infraestrutura Domínio Aplicação Camadas

  32. 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
  33. Casos de uso Ponto de entrada Domínio Infraestrutura Depois

  34. ≋ Camadas ≋ Por ordem de importância

  35. 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
  36. class User { constructor({ name, age }) { this.name =

    name; this.age = age; } isValid() { return Boolean(this.name) && this.isOfAge(); } isOfAge() { return this.age >= 18; } }
  37. 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
  38. 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); } } }
  39. 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); } };
  40. • 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
  41. class UserRepository { async add(user) { const userDbData = UserMapper.toDatabase(user);

    const newUser = await UsersTable.insert(userDbData); return UserMapper.toEntity(newUser); } }
  42. 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; } };
  43. 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)
  44. 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); } };
  45. const createUserAction = (userData) => (dispatch) => { dispatch(startCreateUser()); createUser(userData,

    { onSuccess: (newUser) => dispatch(successCreateUser(newUser)), onError: (error) => dispatch(failCreateUser(error)); }); };
  46. 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
  47. 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
  48. 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
  49. 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
  50. • 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
  51. Também vale para CSS! ITCSS

  52. 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
  53. Show me the code!

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

  55. None
  56. 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 • talyssonoc@gmail.com
  57. 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
  58. Obrigado! @talyssonoc talyssonoc.github.io