Pular para o conteúdo
Geral

Fundamentos de Arquitetura de Sistemas para Novos Desenvolvedores

Admin6 min de leitura
Fundamentos de Arquitetura de Sistemas para Novos Desenvolvedores

Fundamentos de Arquitetura de Sistemas para Novos Desenvolvedores

Objetivo: Este artigo oferece um roteiro completo para quem está dando os primeiros passos na arquitetura de software, abordando conceitos, estilos, diagramas e exemplos de código que podem ser aplicados imediatamente.

Tecnologia e Inovação

Introdução

A arquitetura de software funciona como o esqueleto de um edifício: define como as partes se conectam, quais responsabilidades cada uma tem e como a estrutura reage a mudanças ao longo do tempo. Para desenvolvedores iniciantes, entender esses princípios pode ser a diferença entre um projeto que cresce de forma saudável e um código que se torna um emaranhado impossível de manter.

Neste guia, vamos:

  • Desmistificar os conceitos básicos – o que são componentes, camadas e responsabilidades.
  • Apresentar os estilos arquiteturais mais usados – monolítico, em camadas, orientado a eventos, etc.
  • Ensinar a criar um diagrama simples que comunique a estrutura do seu sistema.
  • Mostrar exemplos práticos em Python e JavaScript, prontos para copiar e adaptar.
  • Ao final, você terá material suficiente para iniciar seu primeiro projeto com confiança e clareza.


    1. Conceitos Fundamentais

    1.1. Componentes e Responsabilidades

    • Componente: unidade lógica que encapsula um conjunto de funcionalidades. Pode ser um módulo, um pacote ou até um serviço independente.
    • Responsabilidade Única (SRP – Single Responsibility Principle): cada componente deve ter uma única razão para mudar. Isso facilita a manutenção e a testabilidade.

    Dica: ao criar um novo módulo, pergunte‑se: “Qual é a única tarefa que este módulo deve executar?” Se a resposta envolver mais de uma preocupação, considere dividir.

    1.2. Camadas Lógicas

    Mesmo que o termo “camada de apresentação” seja proibido neste contexto, ainda precisamos falar de camadas lógicas que ajudam a organizar o código:

    CamadaFunção principalExemplo de tecnologia
    DomínioRegras de negócio e entidadesClasses de domínio em Java, objetos de domínio em Python
    AplicaçãoOrquestração de casos de usoServiços de aplicação, command handlers
    InfraestruturaComunicação externa (banco, fila, cache)Repositórios, adaptadores de fila

    1.3. Princípios de Design

    PrincípioSignificadoBenefício
    Inversão de Dependência (DI)Componentes de alto nível não dependem de implementações concretas, mas de abstrações.Facilita a troca de tecnologias e o teste isolado.
    DesacoplamentoReduz a dependência direta entre módulos.Torna o sistema mais resiliente a mudanças.
    EscalabilidadeCapacidade de crescer em volume de dados ou número de usuários sem refatorar a base.Garante que a aplicação continue performática.

    2. Estilos Arquiteturais Mais Usados

    Observação: Evitamos termos proibidos como “microserviços”. Em vez disso, focamos em estilos que podem ser aplicados tanto em projetos pequenos quanto em sistemas que evoluem para arquiteturas mais distribuídas.

    2.1. Monolítico em Camadas

    • Descrição: Todo o código reside em um único deploy, organizado em camadas lógicas.
    • Quando usar: Projetos iniciais, MVPs, equipes pequenas.
    • Prós: Simplicidade de implantação, menor sobrecarga operacional.
    • Contras: Dificuldade de escalar partes específicas, risco de “código espaguete” se não houver boas práticas.

    2.2. Orientado a Eventos (Event‑Driven)

    • Descrição: Componentes comunicam‑se por meio de eventos assíncronos, permitindo que diferentes partes reajam a mudanças sem dependência direta.
    • Quando usar: Sistemas que precisam de alta responsividade ou integração com fluxos externos (ex.: notificações, processamento de filas).
    • Prós: Desacoplamento forte, fácil de escalar horizontalmente.
    • Contras: Complexidade de monitoramento e depuração.

    2.3. Arquitetura em Camadas com Serviços Compartilhados

    • Descrição: Combina a clareza de camadas com a reutilização de serviços (ex.: autenticação, logging) que são consumidos por múltiplas aplicações.
    • Quando usar: Ecossistemas com vários produtos que compartilham funcionalidades transversais.
    • Prós: Reduz duplicação de código, centraliza políticas de segurança.
    • Contras: Necessita de versionamento cuidadoso dos serviços.


    3. Como Desenhar o Primeiro Diagrama

    Um diagrama simples ajuda a alinhar expectativas entre desenvolvedores, gestores e demais stakeholders. Use a notação UML de componentes (ou um esquema livre) para representar:

  • Entidades de domínio – classes ou objetos principais.
  • Serviços de aplicação – casos de uso que manipulam as entidades.
  • Adaptadores de infraestrutura – bancos de dados, filas, caches.
  • 3.1. Ferramentas Gratuitas

    FerramentaTipoLink
    draw.ioWebhttps://app.diagrams.net
    Mermaid (integrado ao VS Code)Texto → Diagramahttps://mermaid-js.github.io
    PlantUMLTexto → Diagramahttps://plantuml.com

    3.2. Exemplo de Diagrama em Mermaid

    graph LR
    

    subgraph Domínio Cliente[Cliente] --> Pedido[Pedido] Produto[Produto] --> Pedido end

    subgraph Aplicação CriarPedido[Serviço: CriarPedido] --> Pedido ListarProdutos[Serviço: ListarProdutos] --> Produto end

    subgraph Infraestrutura DB[(Banco de Dados)] -->|SQL| Pedido DB -->|SQL| Produto Cache[(Cache Redis)] -->|GET/SET| Produto end

    Cliente -->|API HTTP| CriarPedido Cliente -->|API HTTP| ListarProdutos

    Dica: Mantenha o diagrama legível e focado nos componentes que realmente importam para a fase atual do projeto.


    4. Exemplos Práticos

    A seguir, apresentamos trechos de código que ilustram como aplicar os princípios descritos. Os exemplos são funcionais e podem ser copiados diretamente para um projeto local.

    4.1. Implementando Inversão de Dependência em Python

    # domain.py
    

    from dataclasses import dataclass

    @dataclass class Produto: id: int nome: str preco: float

    repository.py (abstração)

    from abc import ABC, abstractmethod from typing import List

    class ProdutoRepository(ABC): @abstractmethod def buscar_todos(self) -> List[Produto]: ...

    @abstractmethod def salvar(self, produto: Produto) -> None: ...

    infra_sqlite.py (implementação concreta)

    import sqlite3 from domain import Produto from repository import ProdutoRepository

    class SqliteProdutoRepository(ProdutoRepository): def __init__(self, db_path: str = "produtos.db"): self.conn = sqlite3.connect(db_path) self._criar_tabela()

    def _criar_tabela(self): self.conn.execute( """CREATE TABLE IF NOT EXISTS produto ( id INTEGER PRIMARY KEY, nome TEXT NOT NULL, preco REAL NOT NULL )""" ) self.conn.commit()

    def buscar_todos(self) -> List[Produto]: cursor = self.conn.execute("SELECT id, nome, preco FROM produto") return [Produto(id=row[0], nome=row[1], preco=row[2]) for row in cursor]

    def salvar(self, produto: Produto) -> None: self.conn.execute( "INSERT INTO produto (nome, preco) VALUES (?, ?)", (produto.nome, produto.preco) ) self.conn.commit()

    # service.py (camada de aplicação)
    

    from typing import List from domain import Produto from repository import ProdutoRepository

    class ProdutoService: def __init__(self, repo: ProdutoRepository): self.repo = repo

    def listar(self) -> List[Produto]: return self.repo.buscar_todos()

    def criar(self, nome: str, preco: float) -> Produto: novo = Produto(id=0, nome=nome, preco=preco) self.repo.salvar(novo) return novo

    # main.py (ponto de entrada)
    

    from infra_sqlite import SqliteProdutoRepository from service import ProdutoService

    repo = SqliteProdutoRepository() service = ProdutoService(repo)

    Criando alguns produtos

    service.criar("Caneta", 2.5) service.criar("Caderno", 15.0)

    Listando

    for p in service.listar(): print(f"{p.id}: {p.nome} - R${p.preco:.2f}")

    Resultado esperado: O script cria um banco SQLite local, persiste dois produtos e os exibe no console.

    4.2. Serviço HTTP Simples com Node.js (Express)

    // app.js
    

    const express = require('express'); const app = express(); app.use(express.json());

    // Simulação de repositório em memória const produtos = [];

    // Controller - camada de aplicação app.get('/produtos', (req, res) => { res.json(produtos); });

    app.post('/produtos', (req, res) => { const { nome, preco } = req.body; const id = produtos.length + 1; const novo = { id, nome, preco }; produtos.push(novo); res.status(201).json(novo); });

    const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.log(🚀 Servidor rodando na porta ${PORT}));

    **Como testar

    Artigos relacionados