<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>gaGO.io</title>
	<atom:link href="https://gago.io/feed/" rel="self" type="application/rss+xml" />
	<link>https://gago.io</link>
	<description>Developer, Architect, Blogger, Speaker...</description>
	<lastBuildDate>Wed, 11 Mar 2026 03:31:38 +0000</lastBuildDate>
	<language>pt-BR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://gago.io/wp-content/uploads/2015/06/Oragon-Architecture-Penknife.export-5573b448v1_site_icon-32x32.png</url>
	<title>gaGO.io</title>
	<link>https://gago.io</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Agent Skill &#8211; O elo que faltava para entregar precisão aos code agents</title>
		<link>https://gago.io/blog/agent-skill-01/</link>
					<comments>https://gago.io/blog/agent-skill-01/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Tue, 10 Mar 2026 16:59:07 +0000</pubDate>
				<category><![CDATA[AI Coding Agents]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Agent Skills]]></category>
		<category><![CDATA[Anthropic Claude Code]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20927</guid>

					<description><![CDATA[<p>Você já viu dezenas de vídeos sobre Agent Skills. Leu posts, explorou repositórios, talvez até tenha criado uma ou duas. E mesmo assim, na hora de usar num projeto real, o resultado continua genérico, impreciso, frustrante. O problema não é o conceito. O problema é que a maioria do conteúdo que existe sobre Agent Skills [&#8230;]</p>
The post <a href="https://gago.io/blog/agent-skill-01/">Agent Skill – O elo que faltava para entregar precisão aos code agents</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Você já viu dezenas de vídeos sobre Agent Skills. Leu posts, explorou repositórios, talvez até tenha criado uma ou duas. E mesmo assim, na hora de usar num projeto real, o resultado continua <strong>genérico</strong>, <strong>impreciso</strong>, <strong>frustrante</strong>.</p>



<p>O problema não é o conceito. O problema é que a maioria do conteúdo que existe sobre Agent Skills é tão raso quanto as skills que ensina a criar.</p>



<p>Se você já julgou que LLM&#8217;s não entregam qualidade suficiente, ou acredita que o código resultante de uma LLM é ruim, se você já passou por isso e ainda não entendeu como skills podem, de fato, mudar a qualidade do que a IA entrega no seu projeto: Esse post é para você.</p>



<span id="more-20927"></span>



<h2 class="wp-block-heading">Antes de tudo, dois fundamentos</h2>



<h3 class="wp-block-heading">Os modelos LLM possuem &#8220;todo o conhecimento do mundo&#8221; </h3>



<p>Essa afirmação é exagerada, mas não da forma como você imagina.</p>



<p>Do ponto de vista de um projeto de software, o volume de conteúdo ingerido na fase de treinamento é vasto o suficiente para construir qualquer coisa mainstream. É mais que suficiente para quase tudo que já fizemos e tudo que ainda vamos fazer no mercado.</p>



<p>Se você não trabalha para uma agência governamental com uma cadeia de pesquisa ultrassecreta, provavelmente tudo que você quer fazer é factível.</p>



<p class="has-text-align-right"><em><strong> e factível de se fazer bem feito com ajuda da IA</strong></em>.</p>



<p>Mas &#8220;bem feito&#8221; não é um exercício fácil, muito menos barato. Perde-se tempo, gasta-se dinheiro, e o resultado oscila entre o aceitável e o medíocre.</p>



<p>Agent Skills mudaram isso no meu dia a dia. Entender o porquê é o primeiro passo.</p>



<h3 class="wp-block-heading">Skills são receitas de foco</h3>



<p>Um modelo LLM tem conhecimento sobre praticamente tudo, mas justamente por isso, precisa de direcionamento para entregar algo preciso. É como pedir para alguém que sabe cozinhar qualquer coisa preparar &#8220;algo bom&#8221;: Sem receita, sem ingredientes definidos, sem restrições. O resultado vai ser comestível, mas dificilmente vai ser o que você queria.</p>



<p>Agent Skills são receitas que conduzem o modelo a filtrar o que ele sabe e focar estritamente no que seu projeto precisa. Uma skill pode ser genérica ou absolutamente específica. </p>



<p>E é na especificidade que mora a diferença entre resultado aceitável e resultado preciso.</p>



<h3 class="wp-block-heading">Skill não é tool, tool não é feature</h3>



<p>Uma <strong>tool</strong> é uma capacidade atômica. Um único comando que o agente consegue executar: rodar um bash, buscar na web, ler um arquivo, fazer uma chamada HTTP. Ferramentas são martelos, serras e furadeiras.</p>



<p>Uma <strong>skill</strong> é um conjunto de instruções que ensina o agente a <strong>orquestrar múltiplas tools</strong> para cumprir uma tarefa complexa com qualidade. A skill não dá capacidades novas ao modelo. Ela dá <strong>expertise na combinação das capacidades que já existem</strong>. Skills são receitas que dizem quais ferramentas usar, em que ordem, e quais erros evitar.</p>



<p>Uma <strong>feature</strong> é um conceito de produto — algo que o usuário vê e pode ligar ou desligar. &#8220;Code Execution&#8221;, &#8220;Web Search&#8221;, &#8220;Artifacts&#8221; são features. Cada feature é alimentada por uma combinação de tools e skills trabalhando juntas.</p>



<p>A skill do <code>docx</code>, por exemplo, não dá ao Claude a capacidade de criar Word. Ele já consegue. O que ela faz é ensinar que o <code>docx-js</code> usa A4 por padrão, que bullets unicode quebram em certos viewers, que o page size precisa ser explícito. Sem a skill, o resultado é tecnicamente funcional. Com a skill, é profissional.</p>



<h3 class="wp-block-heading">Exemplos de Skills</h3>



<p>Note quão abstrata são as skills mais famosas, ou mais usadas do mercado</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Skill nano-banana-pro" data-enlighter-group="exemplo-skills">---
name: nano-banana-pro
description: Generate or edit images via Gemini 3 Pro Image (Nano Banana Pro).
homepage: https://ai.google.dev/
metadata:
  {
    "openclaw":
      {
        "emoji": "🍌",
        "requires": { "bins": ["uv"], "env": ["GEMINI_API_KEY"] },
        "primaryEnv": "GEMINI_API_KEY",
        "install":
          [
            {
              "id": "uv-brew",
              "kind": "brew",
              "formula": "uv",
              "bins": ["uv"],
              "label": "Install uv (brew)",
            },
          ],
      },
  }
---

# Nano Banana Pro (Gemini 3 Pro Image)

Use the bundled script to generate or edit images.

Generate

```bash
uv run {baseDir}/scripts/generate_image.py --prompt "your image description" --filename "output.png" --resolution 1K
```

Edit (single image)

```bash
uv run {baseDir}/scripts/generate_image.py --prompt "edit instructions" --filename "output.png" -i "/path/in.png" --resolution 2K
```

Multi-image composition (up to 14 images)

```bash
uv run {baseDir}/scripts/generate_image.py --prompt "combine these into one scene" --filename "output.png" -i img1.png -i img2.png -i img3.png
```

API key

- `GEMINI_API_KEY` env var
- Or set `skills."nano-banana-pro".apiKey` / `skills."nano-banana-pro".env.GEMINI_API_KEY` in `~/.openclaw/openclaw.json`

Notes

- Resolutions: `1K` (default), `2K`, `4K`.
- Use timestamps in filenames: `yyyy-mm-dd-hh-mm-ss-name.png`.
- The script prints a `MEDIA:` line for OpenClaw to auto-attach on supported chat providers.
- Do not read the image back; report the saved path only.
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Skill nano-banana-pro | resource generate_image.py" data-enlighter-group="exemplo-skills">#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "google-genai>=1.0.0",
#     "pillow>=10.0.0",
# ]
# ///
"""
Generate images using Google's Nano Banana Pro (Gemini 3 Pro Image) API.

Usage:
    uv run generate_image.py --prompt "your image description" --filename "output.png" [--resolution 1K|2K|4K] [--api-key KEY]

Multi-image editing (up to 14 images):
    uv run generate_image.py --prompt "combine these images" --filename "output.png" -i img1.png -i img2.png -i img3.png
"""

import argparse
import os
import sys
from pathlib import Path


def get_api_key(provided_key: str | None) -> str | None:
    """Get API key from argument first, then environment."""
    if provided_key:
        return provided_key
    return os.environ.get("GEMINI_API_KEY")


def main():
    parser = argparse.ArgumentParser(
        description="Generate images using Nano Banana Pro (Gemini 3 Pro Image)"
    )
    parser.add_argument(
        "--prompt", "-p",
        required=True,
        help="Image description/prompt"
    )
    parser.add_argument(
        "--filename", "-f",
        required=True,
        help="Output filename (e.g., sunset-mountains.png)"
    )
    parser.add_argument(
        "--input-image", "-i",
        action="append",
        dest="input_images",
        metavar="IMAGE",
        help="Input image path(s) for editing/composition. Can be specified multiple times (up to 14 images)."
    )
    parser.add_argument(
        "--resolution", "-r",
        choices=["1K", "2K", "4K"],
        default="1K",
        help="Output resolution: 1K (default), 2K, or 4K"
    )
    parser.add_argument(
        "--api-key", "-k",
        help="Gemini API key (overrides GEMINI_API_KEY env var)"
    )

    args = parser.parse_args()

    # Get API key
    api_key = get_api_key(args.api_key)
    if not api_key:
        print("Error: No API key provided.", file=sys.stderr)
        print("Please either:", file=sys.stderr)
        print("  1. Provide --api-key argument", file=sys.stderr)
        print("  2. Set GEMINI_API_KEY environment variable", file=sys.stderr)
        sys.exit(1)

    # Import here after checking API key to avoid slow import on error
    from google import genai
    from google.genai import types
    from PIL import Image as PILImage

    # Initialise client
    client = genai.Client(api_key=api_key)

    # Set up output path
    output_path = Path(args.filename)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    # Load input images if provided (up to 14 supported by Nano Banana Pro)
    input_images = []
    output_resolution = args.resolution
    if args.input_images:
        if len(args.input_images) > 14:
            print(f"Error: Too many input images ({len(args.input_images)}). Maximum is 14.", file=sys.stderr)
            sys.exit(1)

        max_input_dim = 0
        for img_path in args.input_images:
            try:
                with PILImage.open(img_path) as img:
                    copied = img.copy()
                    width, height = copied.size
                input_images.append(copied)
                print(f"Loaded input image: {img_path}")

                # Track largest dimension for auto-resolution
                max_input_dim = max(max_input_dim, width, height)
            except Exception as e:
                print(f"Error loading input image '{img_path}': {e}", file=sys.stderr)
                sys.exit(1)

        # Auto-detect resolution from largest input if not explicitly set
        if args.resolution == "1K" and max_input_dim > 0:  # Default value
            if max_input_dim >= 3000:
                output_resolution = "4K"
            elif max_input_dim >= 1500:
                output_resolution = "2K"
            else:
                output_resolution = "1K"
            print(f"Auto-detected resolution: {output_resolution} (from max input dimension {max_input_dim})")

    # Build contents (images first if editing, prompt only if generating)
    if input_images:
        contents = [*input_images, args.prompt]
        img_count = len(input_images)
        print(f"Processing {img_count} image{'s' if img_count > 1 else ''} with resolution {output_resolution}...")
    else:
        contents = args.prompt
        print(f"Generating image with resolution {output_resolution}...")

    try:
        response = client.models.generate_content(
            model="gemini-3-pro-image-preview",
            contents=contents,
            config=types.GenerateContentConfig(
                response_modalities=["TEXT", "IMAGE"],
                image_config=types.ImageConfig(
                    image_size=output_resolution
                )
            )
        )

        # Process response and convert to PNG
        image_saved = False
        for part in response.parts:
            if part.text is not None:
                print(f"Model response: {part.text}")
            elif part.inline_data is not None:
                # Convert inline data to PIL Image and save as PNG
                from io import BytesIO

                # inline_data.data is already bytes, not base64
                image_data = part.inline_data.data
                if isinstance(image_data, str):
                    # If it's a string, it might be base64
                    import base64
                    image_data = base64.b64decode(image_data)

                image = PILImage.open(BytesIO(image_data))

                # Ensure RGB mode for PNG (convert RGBA to RGB with white background if needed)
                if image.mode == 'RGBA':
                    rgb_image = PILImage.new('RGB', image.size, (255, 255, 255))
                    rgb_image.paste(image, mask=image.split()[3])
                    rgb_image.save(str(output_path), 'PNG')
                elif image.mode == 'RGB':
                    image.save(str(output_path), 'PNG')
                else:
                    image.convert('RGB').save(str(output_path), 'PNG')
                image_saved = True

        if image_saved:
            full_path = output_path.resolve()
            print(f"\nImage saved: {full_path}")
            # OpenClaw parses MEDIA tokens and will attach the file on supported providers.
            print(f"MEDIA: {full_path}")
        else:
            print("Error: No image was generated in the response.", file=sys.stderr)
            sys.exit(1)

    except Exception as e:
        print(f"Error generating image: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Skill blogwatcher" data-enlighter-group="exemplo-skills">---
name: blogwatcher
description: Monitor blogs and RSS/Atom feeds for updates using the blogwatcher CLI.
homepage: https://github.com/Hyaxia/blogwatcher
metadata:
  {
    "openclaw":
      {
        "emoji": "📰",
        "requires": { "bins": ["blogwatcher"] },
        "install":
          [
            {
              "id": "go",
              "kind": "go",
              "module": "github.com/Hyaxia/blogwatcher/cmd/blogwatcher@latest",
              "bins": ["blogwatcher"],
              "label": "Install blogwatcher (go)",
            },
          ],
      },
  }
---

# blogwatcher

Track blog and RSS/Atom feed updates with the `blogwatcher` CLI.

Install

- Go: `go install github.com/Hyaxia/blogwatcher/cmd/blogwatcher@latest`

Quick start

- `blogwatcher --help`

Common commands

- Add a blog: `blogwatcher add "My Blog" https://example.com`
- List blogs: `blogwatcher blogs`
- Scan for updates: `blogwatcher scan`
- List articles: `blogwatcher articles`
- Mark an article read: `blogwatcher read 1`
- Mark all articles read: `blogwatcher read-all`
- Remove a blog: `blogwatcher remove "My Blog"`

Example output

```
$ blogwatcher blogs
Tracked blogs (1):

  xkcd
    URL: https://xkcd.com
```

```
$ blogwatcher scan
Scanning 1 blog(s)...

  xkcd
    Source: RSS | Found: 4 | New: 4

Found 4 new article(s) total!
```

Notes

- Use `blogwatcher &lt;command> --help` to discover flags and options.
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Skill acp-router" data-enlighter-group="exemplo-skills">---
name: acp-router
description: Route plain-language requests for Pi, Claude Code, Codex, OpenCode, Gemini CLI, or ACP harness work into either OpenClaw ACP runtime sessions or direct acpx-driven sessions ("telephone game" flow). For coding-agent thread requests, read this skill first, then use only `sessions_spawn` for thread creation.
user-invocable: false
---

# ACP Harness Router

When user intent is "run this in Pi/Claude Code/Codex/OpenCode/Gemini/Kimi (ACP harness)", do not use subagent runtime or PTY scraping. Route through ACP-aware flows.

## Intent detection

Trigger this skill when the user asks OpenClaw to:

- run something in Pi / Claude Code / Codex / OpenCode / Gemini
- continue existing harness work
- relay instructions to an external coding harness
- keep an external harness conversation in a thread-like conversation

Mandatory preflight for coding-agent thread requests:

- Before creating any thread for Pi/Claude/Codex/OpenCode/Gemini work, read this skill first in the same turn.
- After reading, follow `OpenClaw ACP runtime path` below; do not use `message(action="thread-create")` for ACP harness thread spawn.

## Mode selection

Choose one of these paths:

1. OpenClaw ACP runtime path (default): use `sessions_spawn` / ACP runtime tools.
2. Direct `acpx` path (telephone game): use `acpx` CLI through `exec` to drive the harness session directly.

Use direct `acpx` when one of these is true:

- user explicitly asks for direct `acpx` driving
- ACP runtime/plugin path is unavailable or unhealthy
- the task is "just relay prompts to harness" and no OpenClaw ACP lifecycle features are needed

Do not use:

- `subagents` runtime for harness control
- `/acp` command delegation as a requirement for the user
- PTY scraping of pi/claude/codex/opencode/gemini/kimi CLIs when `acpx` is available

## AgentId mapping

Use these defaults when user names a harness directly:

- "pi" -> `agentId: "pi"`
- "claude" or "claude code" -> `agentId: "claude"`
- "codex" -> `agentId: "codex"`
- "opencode" -> `agentId: "opencode"`
- "gemini" or "gemini cli" -> `agentId: "gemini"`
- "kimi" or "kimi cli" -> `agentId: "kimi"`

These defaults match current acpx built-in aliases.

If policy rejects the chosen id, report the policy error clearly and ask for the allowed ACP agent id.

## OpenClaw ACP runtime path

Required behavior:

1. For ACP harness thread spawn requests, read this skill first in the same turn before calling tools.
2. Use `sessions_spawn` with:
   - `runtime: "acp"`
   - `thread: true`
   - `mode: "session"` (unless user explicitly wants one-shot)
3. For ACP harness thread creation, do not use `message` with `action=thread-create`; `sessions_spawn` is the only thread-create path.
4. Put requested work in `task` so the ACP session gets it immediately.
5. Set `agentId` explicitly unless ACP default agent is known.
6. Do not ask user to run slash commands or CLI when this path works directly.

Example:

User: "spawn a test codex session in thread and tell it to say hi"

Call:

```json
{
  "task": "Say hi.",
  "runtime": "acp",
  "agentId": "codex",
  "thread": true,
  "mode": "session"
}
```

## Thread spawn recovery policy

When the user asks to start a coding harness in a thread (for example "start a codex/claude/pi/kimi thread"), treat that as an ACP runtime request and try to satisfy it end-to-end.

Required behavior when ACP backend is unavailable:

1. Do not immediately ask the user to pick an alternate path.
2. First attempt automatic local repair:
   - ensure plugin-local pinned acpx is installed in `extensions/acpx`
   - verify `${ACPX_CMD} --version`
3. After reinstall/repair, restart the gateway and explicitly offer to run that restart for the user.
4. Retry ACP thread spawn once after repair.
5. Only if repair+retry fails, report the concrete error and then offer fallback options.

When offering fallback, keep ACP first:

- Option 1: retry ACP spawn after showing exact failing step
- Option 2: direct acpx telephone-game flow

Do not default to subagent runtime for these requests.

## ACPX install and version policy (direct acpx path)

For this repo, direct `acpx` calls must follow the same pinned policy as the `@openclaw/acpx` extension.

1. Prefer plugin-local binary, not global PATH:
   - `./extensions/acpx/node_modules/.bin/acpx`
2. Resolve pinned version from extension dependency:
   - `node -e "console.log(require('./extensions/acpx/package.json').dependencies.acpx)"`
3. If binary is missing or version mismatched, install plugin-local pinned version:
   - `cd extensions/acpx &amp;&amp; npm install --omit=dev --no-save acpx@&lt;pinnedVersion>`
4. Verify before use:
   - `./extensions/acpx/node_modules/.bin/acpx --version`
5. If install/repair changed ACPX artifacts, restart the gateway and offer to run the restart.
6. Do not run `npm install -g acpx` unless the user explicitly asks for global install.

Set and reuse:

```bash
ACPX_CMD="./extensions/acpx/node_modules/.bin/acpx"
```

## Direct acpx path ("telephone game")

Use this path to drive harness sessions without `/acp` or subagent runtime.

### Rules

1. Use `exec` commands that call `${ACPX_CMD}`.
2. Reuse a stable session name per conversation so follow-up prompts stay in the same harness context.
3. Prefer `--format quiet` for clean assistant text to relay back to user.
4. Use `exec` (one-shot) only when the user wants one-shot behavior.
5. Keep working directory explicit (`--cwd`) when task scope depends on repo context.

### Session naming

Use a deterministic name, for example:

- `oc-&lt;harness>-&lt;conversationId>`

Where `conversationId` is thread id when available, otherwise channel/conversation id.

### Command templates

Persistent session (create if missing, then prompt):

```bash
${ACPX_CMD} codex sessions show oc-codex-&lt;conversationId> \
  || ${ACPX_CMD} codex sessions new --name oc-codex-&lt;conversationId>

${ACPX_CMD} codex -s oc-codex-&lt;conversationId> --cwd &lt;workspacePath> --format quiet "&lt;prompt>"
```

One-shot:

```bash
${ACPX_CMD} codex exec --cwd &lt;workspacePath> --format quiet "&lt;prompt>"
```

Cancel in-flight turn:

```bash
${ACPX_CMD} codex cancel -s oc-codex-&lt;conversationId>
```

Close session:

```bash
${ACPX_CMD} codex sessions close oc-codex-&lt;conversationId>
```

### Harness aliases in acpx

- `pi`
- `claude`
- `codex`
- `opencode`
- `gemini`
- `kimi`

### Built-in adapter commands in acpx

Defaults are:

- `pi -> npx pi-acp`
- `claude -> npx -y @zed-industries/claude-agent-acp`
- `codex -> npx @zed-industries/codex-acp`
- `opencode -> npx -y opencode-ai acp`
- `gemini -> gemini`
- `kimi -> kimi acp`

If `~/.acpx/config.json` overrides `agents`, those overrides replace defaults.

### Failure handling

- `acpx: command not found`:
  - for thread-spawn ACP requests, install plugin-local pinned acpx in `extensions/acpx` immediately
  - restart gateway after install and offer to run the restart automatically
  - then retry once
  - do not ask for install permission first unless policy explicitly requires it
  - do not install global `acpx` unless explicitly requested
- adapter command missing (for example `claude-agent-acp` not found):
  - for thread-spawn ACP requests, first restore built-in defaults by removing broken `~/.acpx/config.json` agent overrides
  - then retry once before offering fallback
  - if user wants binary-based overrides, install exactly the configured adapter binary
- `NO_SESSION`: run `${ACPX_CMD} &lt;agent> sessions new --name &lt;sessionName>` then retry prompt.
- queue busy: either wait for completion (default) or use `--no-wait` when async behavior is explicitly desired.

### Output relay

When relaying to user, return the final assistant text output from `acpx` command result. Avoid relaying raw local tool noise unless user asked for verbose logs.
</pre>



<p>Escolhi 3 exemplos super expressivos:</p>



<h4 class="wp-block-heading">acp-router</h4>



<p>Esse skill é um <strong>roteador</strong> para o &#8220;OpenClaw&#8221; que intercepta pedidos do tipo &#8220;rode isso no Claude Code / Codex / Gemini / Pi / OpenCode / Kimi&#8221; e os encaminha para sessões ACP (Agent Communication Protocol) em vez de usar scraping de terminal ou subagentes genéricos.</p>



<p>Em resumo, ele faz três coisas:</p>



<ol class="wp-block-list">
<li><strong>Detecta a intenção</strong> de usar um coding agent externo (Pi, Claude Code, Codex, etc.) e mapeia o nome para um <code>agentId</code> padronizado.</li>



<li><strong>Escolhe o caminho de execução</strong>: ou via runtime ACP nativo do OpenClaw (<code>sessions_spawn</code>), ou via CLI <code>acpx</code> diretamente (o &#8220;telephone game&#8221;, onde o OpenClaw repassa prompts para o harness e devolve as respostas).</li>



<li><strong>Gerencia ciclo de vida</strong>: criação de sessões persistentes com nomes determinísticos, recuperação automática quando o backend ACP falha (reinstala o <code>acpx</code> local, reinicia o gateway, tenta de novo), e fallback ordenado só se tudo falhar.</li>
</ol>



<p>Basicamente é a cola que permite ao OpenClaw orquestrar múltiplos agentes de código externos de forma padronizada, sem que o usuário precise lidar com CLI ou comandos manuais.</p>



<p>Se não faz ideia do que seja ACP, estamos falando de Agent Communication Protocol <a href="https://agentcommunicationprotocol.dev/introduction/welcome" target="_blank" rel="noopener nofollow sponsored ugc" title="saiba mais">saiba mais</a>.</p>



<h4 class="wp-block-heading">blogwatcher</h4>



<p>Essa skill é um wrapper para o CLI <code>blogwatcher</code> — uma ferramenta em Go que monitora blogs e feeds RSS/Atom.</p>



<p>Ela permite ao agente adicionar blogs para acompanhar, escanear por novos posts, listar artigos encontrados e marcá-los como lidos, tudo via linha de comando. É basicamente um leitor de RSS minimalista operado pelo agente, útil para ficar de olho em atualizações de blogs técnicos ou qualquer fonte com feed.</p>



<h4 class="wp-block-heading">nano-banana-pro</h4>



<p>Essa skill é um wrapper para geração e edição de imagens usando o Gemini 3 Pro (codinome interno &#8220;Nano Banana Pro&#8221;).</p>



<p>Ela expõe um script Python (rodado via <code>uv</code>) que aceita prompts de texto para gerar imagens do zero, editar uma imagem existente com instruções, ou compor múltiplas imagens (até 14) numa cena só. Suporta resoluções de 1K a 4K e usa a API key do Gemini. O agente só precisa informar o prompt e o nome do arquivo de saída.</p>



<p>A aparente simplicidade dos exemplos esconde o ponto central. O modelo já sabe o que é uma CLI, sabe interagir com um MCP, sabe chamar tools, sabe executar projetos .NET, Python, Node. Tudo que a skill precisa dizer é <strong>como</strong>, diante da vastidão de conhecimento, executar estritamente aquela operação para cumprir a tarefa. Skills são mecanismos de foco, de atenção, de redução de escopo.</p>



<h2 class="wp-block-heading">É aqui que a mágica acontece</h2>



<p>Embora o modelo seja treinado com código bom, código médio, mas potencialmente majoritariamente com código ruim, você consegue entregar, via skills tudo que o modelo precisa para realizar, no detalhe, com perfeição:</p>



<ul class="wp-block-list">
<li>Da forma como você deseja</li>



<li>Com o nível de detalhe que você precisa.</li>



<li>Independente do que você esteja fazendo</li>
</ul>



<h3 class="wp-block-heading">Skill não é sinônimo de arquivo markdown</h3>



<p>A maioria dos exemplos que circulam por aí são arquivos <code>.md</code> com instruções em linguagem natural. É o formato mais comum, mas não é o único.</p>



<p>Uma skill é, no fundo, <strong>expertise codificada sobre como executar uma tarefa bem</strong>. Essa expertise pode viver em vários formatos: </p>



<ul class="wp-block-list">
<li>um arquivo markdown com instruções</li>



<li>um script Python que executa a tarefa diretamente</li>



<li>um JSON schema</li>



<li>um conjunto de exemplos de input/output</li>



<li>ou uma combinação de tudo isso.</li>
</ul>



<p>Markdown funciona bem quando a tarefa envolve ambiguidade e julgamento, quando você precisa que o modelo raciocine sobre o que fazer. Scripts diretos funcionam melhor quando você precisa de execução determinística, sem variação, toda vez. Na prática, o design mais robusto é <strong>híbrido</strong>: um script que faz o trabalho pesado, com um markdown que ensina o agente quando invocar e como interpretar os resultados.</p>



<h2 class="wp-block-heading">Como eu descobri isso?</h2>



<p>Embora eu só tenha de fato entrado de cabeça para usar IA agora em FEV/2026, em outubro 16/OUT com o lançamento das Claude Skills, agora Agent Skills, <strong>ficou claro que era possível assumir o controle, com precisão, do resultado</strong>.</p>



<p>De fato eu não tenho muito interesse em que a IA faça o que eu não sei fazer, <br />eu quero que ela faça o que eu sei fazer, <br />de forma mais rápida e mais eficiente,<br />de tal forma que eu possa corrigir, mas nunca perder o controle.</p>



<p><strong>Controle</strong> é a palavra-chave que explica Skills.</p>



<p>Sempre foi claro para mim, que por melhor que fossem os prompts, a codebase não era suficiente para expressar o padrão ou as intenções, em especial quando se trata de microsserviço, com um nível de automação e infraestrutura elevado.</p>



<p>Quando se alimenta débitos técnicos, pior ainda. </p>



<p>E é essa minha realidade atual.</p>



<p>A IA é o braço para saná-los. Mas como ensiná-la a fazer certo? Ou ignorando certo ou errado, como fazer da forma exata e precisa que eu quero?</p>



<p>Agent Skills parecia a resposta que queria que caísse dos céus.</p>



<p>E de fato era!</p>



<h2 class="wp-block-heading">O mercado escolheu falar e criar skills tão rasas quanto o próprio conhecimento</h2>



<p>O título desse post é provocativo intencionalmente. A maior parte do que vemos no youtube, a maior parte das skills da comunidade, dos repositórios, são skills absolutamente superficiais.</p>



<p>Ao invés de codificar expertise concreta, elas dizem &#8220;Faça da melhor forma seguindo os melhores padrões&#8221;. Nesse momento, você delegou ao modelo a descoberta de qual padrão se aplica, <span style="color: #ff0000;" class="stk-highlight"><strong>e o resultado vai refletir a média do treinamento, não a sua intenção</strong></span>. A skill deixou de ser uma receita e virou um pedido genérico ao chef. <span style="color: #ff0000;" class="stk-highlight">O modelo não ganhou expertise nenhuma. Ganhou liberdade para improvisar</span>, que é exatamente o oposto do que você queria.</p>



<p>E o que é &#8220;boa prática&#8221;?!</p>



<p>Nem todo mundo foi por essa linha, a skill <strong>nano-banana-pro</strong>, do exemplo acima, por exmemplo, aprofunda no detalhe, trazendo inclusive um script python para ser chamado. Isso é controle!</p>



<p>Não é preciso detalhar tudo, mas é importante saber que é possível, que tem como. O óbvio não precisa ser detalhado, mas toda vez que você quer assumir o controle, detalhar se torna fundamental.</p>



<p>E tem um problema que vai além da superficialidade do conteúdo: o <strong>triggering</strong>. Uma skill pode ser tecnicamente excelente, mas se a description for vaga, o agente simplesmente não vai invocá-la. Ele não vai reconhecer que aquela skill se aplica ao contexto atual. A description de uma skill não é um resumo para humanos — é uma instrução de matching para o modelo. Precisa ser específica o suficiente para que o agente identifique, sem ambiguidade, quando usá-la. Esse é mais um ponto que o conteúdo raso não ensina.</p>



<h2 class="wp-block-heading">Exemplo &#8211; API Gateway</h2>



<p>Vou detalhar até que ponto conseguimos entregar detalhes para a IA.</p>



<h3 class="wp-block-heading">Conexto</h3>



<p>Meu primeiro contato real, e intencional com os Code Agents se deu depois de meses de projeto, há praticamente 2 meses de entrar em produção. </p>



<h3 class="wp-block-heading">A demanda por automação</h3>



<p>O projeto tem muita coisa que foge ao padrão dos projetos que encontramos nas empresas daqui do Brasil e em especial no github. </p>



<p>Por acaso, por se tratar de um projeto cloud native, nos moldes do que eu faço e falo há anos, endereça também soluções para as críticas que faço aqui no gago.io, motivo que fez fazer parte do projeto.</p>



<p>Entre outras coisas, nesse tipo de projeto (criação de plataforma), a aplicação precisa tocar na infraestrutura para reconfigurar a infraestrutura em virtude de operações de negócio.</p>



<p>Porque não é concebível ou aceitável que em pleno 2026 quando um cliente é cadastrado na plataforma e cria uma API Key, um humano precise apertar um botão para aplicar essa api key no api gateway.</p>



<p><em>Imagina se cada vez que você cria uma API Key na <strong>Anthropic</strong>, <strong>OpenAI</strong>, <strong>Google</strong>, <strong>Microsoft</strong>, um humano precisasse replicar isso no gateway via algum tipo de botão ou script. </em></p>



<p>Na visão de produto, é inaceitável. </p>



<ul class="wp-block-list">
<li>A criação da API KEY</li>



<li>A sincronização com o API SIX</li>



<li>A criação do container do cliente</li>
</ul>



<p>todas essas tarefas devem acontecer em multiplos datacenters, ao vivo, instantaneamente, e automaticamente, sem humanos como gargalo.</p>



<h3 class="wp-block-heading">Apache APISIX</h3>



<p>Como API Gateway, dessa vez saí do clássico Kong/Konga e optei pelo Apache APISIX e foi a melhor mudança que fiz nos últimos anos. </p>



<ul class="wp-block-list">
<li>Apache APISIX não possui plugins pagos, todos são gratuitos, diferente do Kong, </li>



<li>É mantido sob a guarda da Apache Software Foundation</li>
</ul>



<p>Esses foram os principais elementos que me fizeram dar uma chance, ainda no Academia Pay, e deu muito certo!</p>



<p>Como esse foi meu primeiro projeto depois do Academia Pay, fez muito sentido adotá-lo aqui.</p>



<p>As operações básicas de subida da infra acontecem via curl configurados como job no kubernetes. Já na máquina local do dev, um container dos vários do docker compose faz a tarefa.</p>



<p>Mas uma perna precisa ser dinâmica: A criação das API Keys. Essa tarefa não é um curl que resolve é a aplicação então resolvi estruturar isso de forma correta e daí nasce:</p>



<ul class="wp-block-list">
<li>Interface para o Refit</li>



<li>DTO&#8217;s e mais DTO&#8217;s&#8230;</li>
</ul>



<p>Bom, está claro que estamos diante de algo customizado, algo feito sob demanda para um problema específico de um projeto peculiar de plataforma. </p>



<p>Algo que não está no treinamento do modelo. Não exatamente como fizemos aqui.</p>



<h3 class="wp-block-heading">Criando Agent Skills</h3>



<p>Uma vez que você tem uma arquitetura funcional, pedir para o claude code construir uma skill é muito fácil. Eu criei um prompt com as documentações de Skills de todos os principais providers de agentes.</p>



<ul class="wp-block-list">
<li>https://agentskills.io/home</li>



<li>https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview</li>



<li>https://developers.openai.com/codex/skills/</li>



<li>https://manus.im/pt-br/features/agent-skills</li>



<li>https://learn.microsoft.com/en-us/agent-framework/agents/skills</li>



<li>https://code.visualstudio.com/docs/copilot/customization/agent-skills</li>



<li>https://opencode.ai/docs/skills/</li>



<li>https://cursor.com/pt-BR/docs/skills</li>
</ul>



<p class="has-text-align-right has-small-font-size"><em>(na época a lista era bem menor, 5 links, sendo 2 da agentskills.io e 2 da platform.claude.com)</em></p>



<p>Eu fiz o trabalho de ir ao google, achar os principais links que explicam Agent Skills, e pedi para, com base na codebase e na documentação, criar skills para essa integração e esse foi o resultado.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Header" data-enlighter-group="apisix-skill">---
name: apisix-gateway
description: Configure and manage ApiSix gateway for API authentication, rate limiting, and consumer management. Use when working with API keys, rate limits, or gateway configuration.
context: fork
agent: Explore
---

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Overview" data-enlighter-group="apisix-skill">## Overview

ApiSix is the API gateway that sits in front of xxxxxxxxxx, handling authentication via API keys, rate limiting, and request transformation. Consumers (representing API keys) are automatically synchronized from xxxxxxxxxx's database to ApiSix via the Event Driven Architecture.

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="When to Use This Skill" data-enlighter-group="apisix-skill">## When to Use This Skill

Use this skill when:
- Configuring new API routes in ApiSix
- Troubleshooting 401/429 errors
- Understanding API key authentication flow
- Modifying rate limiting behavior
- Adding new plugins to routes
- Managing consumers manually

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Architecture Overview" data-enlighter-group="apisix-skill">## Architecture Overview

```
┌─────────────────────────────────────────────────────────────────────────────┐
│ API Request with X-API-Key header                                           │
└─────────────────────────────────────────────────────────────────────────────┘
                                     ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ ApiSix Gateway (Port 7777)                                                  │
│                                                                             │
│ ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐               │
│ │   key-auth      │→ │   limit-count   │→ │ attach-consumer │               │
│ │   plugin        │  │   plugin        │  │ -label plugin   │               │
│ └─────────────────┘  └─────────────────┘  └─────────────────┘               │
│                                                                             │
│ Validates API key    Checks rate limit   Injects consumer                   │
│ Loads consumer       per consumer        labels as headers                  │
└─────────────────────────────────────────────────────────────────────────────┘
                                     ↓
┌─────────────────────────────────────────────────────────────────────────────┐
│ xxxxxxxxxx API (Port 8080)                                                   │
│                                                                             │
│ Receives headers:                                                           │
│ - X-Consumer-Account-Id                                                     │
│ - X-Consumer-Organization-Id                                                │
│ - X-Consumer-Role                                                           │
│ - X-Consumer-Api-Key-Id                                                     │
│ - X-Request-Id                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Key Configuration Files" data-enlighter-group="apisix-skill">## Key Configuration Files

```
compose/apisix/
├── apisix_conf/
│   └── config.yaml          # Base ApiSix configuration
├── apisix-configurer/
│   └── api.sh               # Route and plugin setup scripts
└── docker-compose.yaml      # Local development
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Consumer Synchronization Flow" data-enlighter-group="apisix-skill">## Consumer Synchronization Flow

Consumers are automatically managed via Event Driven Architecture:

```
ApiKey Created/Updated/Deleted in xxxxxxxxxx
              ↓
Event: apikey.{created|changed|deleted}
              ↓
Queue: events.apikey.{event}.flow.work
              ↓
Command: sync-api-key-on-gateway
              ↓
Queue: commands.sync-api-key-on-gateway.work
              ↓
Worker calls IApiSix.CreateConsumerAsync() / DeleteConsumerAsync()
              ↓
ApiSix Admin API: PUT /apisix/admin/consumers/{username}
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Consumer Structure" data-enlighter-group="apisix-skill">## Consumer Structure

### Labels (Metadata)

```csharp
public class Labels
{
    public string ApiKeyId { get; set; }
    public string AccountId { get; set; }
    public string AccountName { get; set; }
    public string OrganizationId { get; set; }
    public string OrganizationName { get; set; }
    public string Role { get; set; }      // "Account", "Organization", "System"
    public string Region { get; set; }    // "default"
}
```

### Plugins

```csharp
public class Plugins
{
    [JsonPropertyName("key-auth")]
    public KeyAuthConsumerPlugin KeyAuth { get; set; }

    [JsonPropertyName("limit-count")]
    public LimitCountConsumerPlugin LimitCount { get; set; }
}

public class KeyAuthConsumerPlugin
{
    [JsonPropertyName("key")]
    public string ApiKey { get; set; }  // The raw API key value
}

public class LimitCountConsumerPlugin
{
    [JsonPropertyName("count")]
    public int Count { get; set; }       // Requests allowed

    [JsonPropertyName("time_window")]
    public int TimeWindow { get; set; }  // Time window in seconds

    [JsonPropertyName("rejected_code")]
    public int RejectedCode { get; set; } = 429;

    [JsonPropertyName("key")]
    public string Key { get; set; } = "remote_addr";
}
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="API Key Format" data-enlighter-group="apisix-skill">## API Key Format

xxxxxxxxxx API keys follow this format:

```
{sys|org|acc}_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Prefix: sys_ – System-level API Key
        org_ – Organization-level API Key
        acc_ – Account-level API Key
Key:    32 random alphanumeric characters
Total:  36 characters
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Create/Update Consumer" data-enlighter-group="apisix-skill">### Create/Update Consumer

```csharp
app.MapQueue("commands.sync-api-key-on-gateway.work", async (
    [FromBody] GenericCommand currentCommand,
    [FromServices] IApiKeyRepository apiKeyRepository,
    [FromServices] IApiSix apiSix,
    [FromServices] ILogger&lt;Program> logger) =>
{
    var apiKeyId = currentCommand.GetMetadata&lt;Guid>(nameof(ApiKey.ApiKeyId));
    var eventName = currentCommand.GetMetadata&lt;string>("EventName");

    if (eventName == "deleted")
    {
        // Delete consumer
        await apiSix.DeleteConsumerAsync(apiKeyId.ToString("N"));
        logger.LogInformation("Consumer deleted: {ApiKeyId}", apiKeyId);
        return AmqpResults.Ack();
    }

    // Load ApiKey with related entities
    var apiKey = await apiKeyRepository.GetOneAsync(apiKeyId);

    if (apiKey == null)
    {
        logger.LogWarning("ApiKey not found: {ApiKeyId}", apiKeyId);
        return AmqpResults.Nack(false);  // Nack requeue=false to avoid reprocessing
    }

    // Create/Update consumer
    await apiSix.CreateConsumerAsync(new ApiSixCreateConsumerRequest()
    {
        Username = apiKey.ApiKeyId.ToString("N"),
        Labels = new Labels(){ ... },
        Plugins = new Plugins { ... }
    });

    logger.LogInformation(
        "Consumer synced: {ApiKeyId}, Role: {Role}, Limit: {Limit}/{Seconds}s",
        apiKeyId,
        apiKey.Account != null ? "Account" : "Organization",
        apiKey.ResourcePlan.Limit,
        apiKey.ResourcePlan.Seconds);

    return AmqpResults.Ack();
})
.WithSafeRunnerConnection("rabbitmq_events")
.WithDispatchConcurrency(1)
.WithPrefetch(1);
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="IApiSix Interface" data-enlighter-group="apisix-skill">## IApiSix Interface

**File:** `Infrastructure/ApiSix/IApiSix.cs`

```csharp
public interface IApiSix
{
    [Put("/apisix/admin/consumers/{username}")]
    Task&lt;ApiSixResponse> CreateConsumerAsync(
        [AliasAs("username")] string username,
        [Body] ApiSixCreateConsumerRequest request);

    [Delete("/apisix/admin/consumers/{username}")]
    Task&lt;ApiSixResponse> DeleteConsumerAsync(
        [AliasAs("username")] string username);

    [Get("/apisix/admin/consumers/{username}")]
    Task&lt;ApiSixConsumerResponse> GetConsumerAsync(
        [AliasAs("username")] string username);

    [Get("/apisix/admin/consumers")]
    Task&lt;ApiSixConsumersListResponse> ListConsumersAsync();
}
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Route Configuration" data-enlighter-group="apisix-skill">## Route Configuration

**File:** `compose/apisix/apisix-configurer/api.sh`

```bash
#!/bin/bash

ADMIN_KEY="${APISIX_ADMIN_KEY:-xxxxxxxxxxxxxxxxxxxxxxxxxx}"
APISIX_HOST="${APISIX_HOST:-http://apisix:9180}"

# Wait for ApiSix to be ready
until curl -s "${APISIX_HOST}/apisix/status" > /dev/null; do
    echo "Waiting for ApiSix..."
    sleep 2
done

# Create main API route
curl -X PUT "${APISIX_HOST}/apisix/admin/routes/xxxxxxxxxx-api" \
    -H "X-API-KEY: ${ADMIN_KEY}" \
    -H "Content-Type: application/json" \
    -d '{
    "uri": "/api/*",
    "name": "xxxxxxxxxx API",
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "xxxxxxxxxx-api:8080": 1
        },
        "timeout": {
            "connect": 5,
            "send": 60,
            "read": 60
        }
    },
    "plugins": {
        "key-auth": {
            "header": "X-API-Key",
            "hide_credentials": true
        },
        "request-id": {
            "header_name": "X-Request-Id",
            "include_in_response": true,
            "algorithm": "uuid"
        },
        "limit-req": {
            "rate": 1000,
            "burst": 500,
            "rejected_code": 429,
            "key_type": "consumer_name"
        },
        "proxy-rewrite": {
            "headers": {
                "set": {
                    "X-Forwarded-Host": "$host"
                }
            }
        },
        "attach-consumer-label": {
            "headers": [
                "X-Consumer-Account-Id:accountId",
                "X-Consumer-Account-Name:accountName",
                "X-Consumer-Organization-Id:organizationId",
                "X-Consumer-Organization-Name:organizationName",
                "X-Consumer-Role:role",
                "X-Consumer-Api-Key-Id:apiKeyId",
                "X-Consumer-Region:region"
            ]
        }
    }
}'

echo "Route configured successfully"
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Base Configuration" data-enlighter-group="apisix-skill">## Base Configuration

**File:** `compose/apisix/apisix_conf/config.yaml`

```yaml
apisix:
  node_listen: 7777
  enable_admin: true
  admin_key:
    - name: admin
      key: xxxxxxxxxxxxxxxxxxxxxxxxxx
      role: admin

etcd:
  host:
    - "http://etcd:2379"
  prefix: "/apisix"
  timeout: 30

plugins:
  - key-auth
  - limit-count
  - limit-req
  - request-id
  - proxy-rewrite
  - attach-consumer-label

plugin_attr:
  log-rotate:
    interval: 3600
    max_kept: 168
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Debugging ApiSix" data-enlighter-group="apisix-skill">## Debugging ApiSix
### Check Consumer Exists

```bash
# List all consumers
curl -s http://apisix:9180/apisix/admin/consumers \
    -H "X-API-KEY: $ADMIN_KEY" | jq '.list[] | {username, labels}'

# Get specific consumer
curl -s http://apisix:9180/apisix/admin/consumers/{apiKeyId} \
    -H "X-API-KEY: $ADMIN_KEY" | jq
```

### Test Authentication

```bash
# Test valid API key
curl -v https://api.xxxxxxxxxx.io/api/health \
    -H "X-API-Key: mfy_xxxxxxxxxxxxx"

# Check response headers
# - X-Request-Id: generated
# - X-RateLimit-Limit: from consumer
# - X-RateLimit-Remaining: requests left
```

### Check Rate Limit Status

```bash
# Multiple requests to trigger rate limit info
for i in {1..5}; do
    curl -s -I https://api.xxxxxxxxxx.io/api/health \
        -H "X-API-Key: mfy_xxxxxxxxxxxxx" | grep -i ratelimit
done
```

### View ApiSix Logs

```bash
# Docker logs
docker logs apisix --tail 100 -f

# Access log
docker exec apisix tail -f /usr/local/apisix/logs/access.log

# Error log
docker exec apisix tail -f /usr/local/apisix/logs/error.log
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Common Issues" data-enlighter-group="apisix-skill">## Common Issues

### 401 Unauthorized

**Causes:**
- API key not found in header
- Consumer not synchronized to ApiSix
- Invalid API key format

**Debug:**
```bash
# Check header is being sent
curl -v ... -H "X-API-Key: mfy_xxx"

# Check consumer exists
curl http://apisix:9180/apisix/admin/consumers -H "X-API-KEY: $ADMIN_KEY"

# Check event processing
rabbitmqadmin get queue=commands.sync-api-key-on-gateway.work count=10
```

### 429 Too Many Requests

**Causes:**
- Rate limit exceeded
- ResourcePlan has low limits
- Multiple clients sharing same API key

**Debug:**
```bash
# Check rate limit headers
curl -I ... | grep RateLimit

# Check consumer's limit-count config
curl http://apisix:9180/apisix/admin/consumers/{id} | jq '.plugins.limit-count'

# Check ResourcePlan in database
SELECT * FROM resource_plans WHERE resource_plan_id = '...';
```

### Missing X-Consumer-* Headers

**Causes:**
- Consumer labels not set
- attach-consumer-label plugin not configured
- Plugin mapping incorrect

**Debug:**
```bash
# Check consumer labels
curl http://apisix:9180/apisix/admin/consumers/{id} | jq '.labels'

# Check route plugins
curl http://apisix:9180/apisix/admin/routes | jq '.list[] | .plugins["attach-consumer-label"]'
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Adding New Routes" data-enlighter-group="apisix-skill">## Adding New Routes

### Webhook Route (No Auth)

```bash
curl -X PUT "${APISIX_HOST}/apisix/admin/routes/webhooks" \
    -H "X-API-KEY: ${ADMIN_KEY}" \
    -d '{
    "uri": "/webhooks/*",
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "xxxxxxxxxx-api:8080": 1
        }
    },
    "plugins": {
        "request-id": {}
    }
}'
```

### Health Check Route (No Auth)

```bash
curl -X PUT "${APISIX_HOST}/apisix/admin/routes/health" \
    -H "X-API-KEY: ${ADMIN_KEY}" \
    -d '{
    "uri": "/health/*",
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "xxxxxxxxxx-api:8080": 1
        }
    },
    "plugins": {}
}'
```

</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Outros - Fim" data-enlighter-group="apisix-skill">## ResourcePlan Examples

| Plan         | Limit     | Seconds | Description           |
|--------------|-----------|---------|-----------------------|
| Free         |       100 |      60 |           100 req/min |
| Starter      |       500 |      60 |           500 req/min |
| Pro          |      2000 |      60 |          2000 req/min |
| Enterprise   |     10000 |      60 |         10000 req/min |
| Unlimited    |   1000000 |       1 | Effectively unlimited |

## Related Skills

- `event-driven-administration` - How consumers are synchronized
- `infrastructure-architecture` - Full infrastructure flow
- `add-api-endpoint` - Creating protected endpoints
- `debug-issue` - General debugging

## Related Documentation

- `message-flow/references/INFRASTRUCTURE_LAYER.md` - Infrastructure details
- `compose/apisix/` - Configuration files
- Apache APISIX Documentation: https://apisix.apache.org/docs/
</pre>



<p>Essse é o nível de especificidade que é possível alcançar.</p>



<h2 class="wp-block-heading">Coisas importantes</h2>



<h3 class="wp-block-heading">Skill e MCP resolvem problemas diferentes</h3>



<p>Se seu agente já se conecta a fontes de dados via MCP, a pergunta natural é: por que preciso de skills?</p>



<p>MCP é <strong>infraestrutura</strong>. Ele expõe dados e serviços, acesso a bancos, APIs, logs, sistemas internos. MCP é a cozinha profissional: fornece os ingredientes, os utensílios, os equipamentos.</p>



<p>Skills são <strong>orquestração</strong>. Elas definem como o agente deve usar os dados que o MCP entrega para produzir algo de valor. Skills são as receitas que dizem o que fazer com os ingredientes.</p>



<p>Na prática, o design que funciona é: acesso a dados, APIs e retrieval como tools (ou capacidades MCP); lógica de decisão e workflows como skills. A skill do APISIX no exemplo acima é exatamente isso: ela não acessa o APISIX diretamente, ela ensina o agente a orquestrar chamadas Refit, interpretar o fluxo de filas e compor consumers com a estrutura correta.</p>



<h3 class="wp-block-heading">Eu não criei essa skill sozinho</h3>



<p>Essa skill foi criada usando o próprio claude code.</p>



<p>Eu pedi para que analisasse a codebase, enviei links quie explicavam o que eram Agent Skills e qual formato desejado e pronto.</p>



<h3 class="wp-block-heading">Detalhes específicos dessa arquitetura</h3>



<p>Muitos pontos foram respeitados e detalhados.</p>



<ul class="wp-block-list">
<li>Refit</li>



<li>Escolha dos plugins</li>



<li>Escolha da estratégia de configuração.</li>



<li>Ausência de try/catchs</li>



<li>Uso do Oragon.RabbitMQ</li>



<li>Entendimento do fluxo completo de filas (uma cadeia de 3 passos)</li>
</ul>



<p>O fato de que um modelo de linguagem é capaz de olhar minha codebase e produzir essa explicação, já é tãoi admirável quanto assustador.</p>



<p>Considerar que esse arquivo é como um cache, uma memória, que evita a descoberta, e que será usado e respeitado para novas operações, é ainda mais aterrorisante quanto empolgante.</p>



<p>Claro, escolhi o apisix para esse post pois é um assunto limpo do viés da arquitetura adotada no projeto. É um exemplo pouco opinativo. </p>



<p><s>Quase</s> todo projeto, dos do tipo que eu pego, e da maioria avassaladora dos leitores do gago.io são projetos que demandam API Gateways, sejam eles internos, dento da empresa, entre aplicações, ou externo, para o acesso púiblico, portanto esse assunto é parte do dia-a-dia.</p>



<figure class="wp-block-image aligncenter size-full"><img data-dominant-color="776947" data-has-transparency="true" style="--dominant-color: #776947;" fetchpriority="high" decoding="async" width="837" height="606" src="https://gago.io/wp-content/uploads/2026/03/image.png" alt="" class="wp-image-20940 has-transparency" srcset="https://gago.io/wp-content/uploads/2026/03/image.png 837w, https://gago.io/wp-content/uploads/2026/03/image-480x348.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 837px, 100vw" /></figure>



<p>Ao todo foram 31 skills, algumas inclusive com resource, todos construídos com ajuda do Claude Code para serem usados pelo Claude Code com base em fragmentos da codebase.</p>



<p>O poder que as skills entregam para o agente são ilimitados. Permitindo inclusive o direcionamento das decisões.</p>



<figure class="wp-block-image size-full"><img data-dominant-color="292a29" data-has-transparency="true" style="--dominant-color: #292a29;" decoding="async" width="676" height="782" src="https://gago.io/wp-content/uploads/2026/03/image-2.png" alt="" class="wp-image-20956 has-transparency" srcset="https://gago.io/wp-content/uploads/2026/03/image-2.png 676w, https://gago.io/wp-content/uploads/2026/03/image-2-480x555.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 676px, 100vw" /></figure>



<p>ou especificações do que são consideradas boas práticas no seu projeto:</p>



<figure class="wp-block-image size-full"><img data-dominant-color="323232" data-has-transparency="true" style="--dominant-color: #323232;" decoding="async" width="616" height="374" src="https://gago.io/wp-content/uploads/2026/03/image-3.png" alt="" class="wp-image-20957 has-transparency" srcset="https://gago.io/wp-content/uploads/2026/03/image-3.png 616w, https://gago.io/wp-content/uploads/2026/03/image-3-480x291.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 616px, 100vw" /></figure>



<p>esses 2 prints são prints da skill do Oragon.RabbitMQ no projeto. Se quiser ver mais exemplos, como o do Oragon.RabbitMQ, comenta aqui.</p>



<p>De coração espero que seu entendimento sobre agent skills tenha evoluído.</p>



<p class="has-text-align-right">Me conte, mudou sua percepção sobre Agent Skills?</p>The post <a href="https://gago.io/blog/agent-skill-01/">Agent Skill – O elo que faltava para entregar precisão aos code agents</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/agent-skill-01/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Event-driven Architecture: você está reaproveitando eventos ou mentindo para o seu próprio sistema?</title>
		<link>https://gago.io/blog/event-driven-architecture-reaproveitamento-equivocado-eventos/</link>
					<comments>https://gago.io/blog/event-driven-architecture-reaproveitamento-equivocado-eventos/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 23:40:06 +0000</pubDate>
				<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Cloud Native .NET]]></category>
		<category><![CDATA[Oragon Architecture]]></category>
		<category><![CDATA[RabbitMQ para Aplicações .NET]]></category>
		<category><![CDATA[The Microservices Journey]]></category>
		<category><![CDATA[Event Driven]]></category>
		<category><![CDATA[Software Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20944</guid>

					<description><![CDATA[<p>Quantos eventos no seu sistema são emitidos em situações onde o fato que eles representam simplesmente não aconteceu? Um PedidoCriado disparado por uma rotina de correção. Um PagamentoConfirmado emitido por um job de reprocessamento. Um UsuarioCadastrado lançado por uma migração de base legada. O fato não aconteceu, mas o evento foi emitido assim mesmo — [&#8230;]</p>
The post <a href="https://gago.io/blog/event-driven-architecture-reaproveitamento-equivocado-eventos/">Event-driven Architecture: você está reaproveitando eventos ou mentindo para o seu próprio sistema?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Quantos eventos no seu sistema são emitidos em situações onde o fato que eles representam simplesmente não aconteceu?</p>



<p>Um <code>PedidoCriado</code> disparado por uma rotina de correção. Um <code>PagamentoConfirmado</code> emitido por um job de reprocessamento. Um <code>UsuarioCadastrado</code> lançado por uma migração de base legada. O fato não aconteceu, mas o evento foi emitido assim mesmo — porque &#8220;já tem tudo plugado&#8221;.</p>



<p>Essa é a confusão mais destrutiva em event-driven architecture. Não é sobre nomenclatura. É sobre semântica. E quando a semântica quebra, o sistema inteiro passa a operar sobre premissas falsas.</p>



<span id="more-20944"></span>



<h2 class="wp-block-heading">Eventos e Comandos se parecem, mas representam coisas opostas</h2>



<p>Um evento e um comando:</p>



<ul class="wp-block-list">
<li>podem ter o mesmo payload</li>



<li>podem trafegar pela mesma infraestrutura</li>



<li>podem até ter nomes parecidos</li>
</ul>



<p class="has-medium-font-size">Mas a intenção que cada um carrega é fundamentalmente diferente.</p>



<p>Um <strong><span style="color: #ff0000;" class="stk-highlight">evento </span></strong>é a <strong>notificação de um fato</strong>. Algo aconteceu. <code>PedidoCriado</code> significa que um pedido foi criado. Um passado consumado, irreversível. </p>



<p>O produtor do evento </p>



<ul class="wp-block-list">
<li>não sabe quem consome</li>



<li>não espera uma reação específica</li>



<li>não tem responsabilidade sobre o que acontece depois</li>
</ul>



<p>Ele apenas registra que um fato ocorreu.</p>



<p>Um <span style="color: #ff0000;" class="stk-highlight"><strong>comando </strong></span>é um pedido de ação. Algo precisa acontecer. <code>SepararEstoque</code> significa que alguém está solicitando ao serviço de estoque que execute uma operação. <strong><span style="color: #ff0000;" class="stk-highlight">O emissor sabe para quem está enviando, espera uma execução, e assume responsabilidade pela intenção.</span></strong></p>



<p class="has-text-align-center has-white-color has-vivid-cyan-blue-to-vivid-purple-gradient-background has-text-color has-background has-link-color wp-elements-ff7464d92a44ee0244a9b34f7632a692">O evento diz <strong>&#8220;isso aconteceu&#8221;</strong>. O comando diz <strong>&#8220;faça isso&#8221;</strong>.</p>



<p>Essa distinção parece óbvia quando escrita assim. Na prática, a infraestrutura conspira contra ela. O RabbitMQ, o Kafka, o SNS, todos tratam eventos e comandos como mensagens. A ferramenta nivela o que a arquitetura deveria separar. <strong>E quando tudo é &#8220;mensagem&#8221;, a distinção semântica se perde no vocabulário do dia a dia do time.</strong></p>



<h2 class="wp-block-heading">O handler de evento é o adaptador que absorve acoplamento</h2>



<p>Entre o evento e o comando existe uma peça que raramente recebe a atenção que merece: <strong>o handler de evento</strong>.</p>



<p>O handler é quem traduz fato em intenção. </p>



<p>Ele recebe <code>PedidoCriado</code> e emite: </p>



<ul class="wp-block-list">
<li><code>SepararEstoque</code></li>



<li><code>NotificarCliente</code></li>



<li><code>GerarFatura</code></li>
</ul>



<p><span style="color: #ff0000;" class="stk-highlight">Cada handler consome o evento e produz o comando específico para o serviço que precisa agir.</span></p>



<p>Essa tradução não é um detalhe de implementação.<strong> É o mecanismo central que permite a EDA funcionar sem criar um emaranhado de dependências.</strong></p>



<p>Sem o handler como adaptador, você tem <span style="color: #ff0000;" class="stk-highlight">duas alternativas ruins</span>. </p>



<p><span style="color: #ff0000;" class="stk-highlight"><strong>A primeira</strong></span>: o serviço que cria o pedido conhece e invoca diretamente todos os serviços que precisam reagir:  estoque, notificação, faturamento. <strong>A pressão cognitiva sobre esse serviço cresce a cada novo consumidor.</strong> </p>



<p><span style="color: #ff0000;" class="stk-highlight"><strong>A segunda: </strong></span>os serviços consumidores interpretam o evento diretamente como instrução de ação, acoplando sua lógica interna à estrutura de um evento que pertence a outro bounded context.</p>



<p><span style="color: #038f5c;" class="stk-highlight"><strong>O handler elimina os dois problemas</strong>.</span> </p>



<ul class="wp-block-list">
<li>O produtor continua ignorando quem consome. </li>



<li>O consumidor recebe um comando limpo, com a interface que ele mesmo definiu. </li>



<li>O handler é a fronteira onde o acoplamento entre contextos é absorvido e isolado.</li>
</ul>



<figure class="wp-block-image size-large"><img data-dominant-color="efece7" data-has-transparency="true" style="--dominant-color: #efece7;" loading="lazy" decoding="async" width="1024" height="572" src="https://gago.io/wp-content/uploads/2026/03/image-1-1024x572.png" alt="" class="wp-image-20950 has-transparency" srcset="https://gago.io/wp-content/uploads/2026/03/image-1-980x547.png 980w, https://gago.io/wp-content/uploads/2026/03/image-1-480x268.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Cada seta entre o handler e o serviço destino é um comando. </p>



<p>Cada comando </p>



<ul class="wp-block-list">
<li>tem contrato próprio</li>



<li>versionamento próprio</li>



<li>e pode ser invocado de qualquer origem</li>
</ul>



<p> não apenas de um handler de evento.</p>



<h2 class="wp-block-heading">Onde o reaproveitamento é legítimo e onde ele quebra?</h2>



<p>Essa arquitetura tem três pontos com características de reaproveitamento completamente diferentes. Confundir esses três níveis é a origem da maioria dos problemas.</p>



<p><strong>No consumo do evento, o reaproveitamento é o design.</strong></p>



<p class="has-medium-font-size">Um único <code>PedidoCriado</code> pode ter dez handlers diferentes. Cada handler traduz o mesmo fato em um comando diferente para um serviço diferente. </p>



<p>Adicionar um novo comportamento ao sistema significa adicionar um novo handler: </p>



<ul class="wp-block-list">
<li>sem alterar o produtor</li>



<li>sem alterar os handlers existentes</li>
</ul>



<p>Open/closed principle aplicado na arquitetura, não apenas no código.</p>



<h3 class="wp-block-heading"><strong>Na emissão do evento, o reaproveitamento é uma mentira.</strong></h3>



<p><strong>O evento está amarrado ao fato que o originou</strong>. <code>PedidoCriado</code> só pode ser emitido quando um pedido é efetivamente criado. </p>



<p>Se você precisa disparar separação de estoque por outro motivo como: </p>



<ul class="wp-block-list">
<li>uma correção manual</li>



<li>um reprocessamento</li>



<li>uma migração </li>
</ul>



<p>emitir <code>PedidoCriado</code> é mentir para o sistema. <strong>O pedido não foi criado.</strong> Todos os handlers vão reagir como se tivesse sido. O cliente vai receber uma notificação de pedido que ele não fez. O faturamento vai gerar um documento fiscal duplicado. A semântica contamina todo o fluxo downstream.</p>



<h3 class="wp-block-heading"><strong>Nos comandos, o reaproveitamento é monumental.</strong></h3>



<p><code>SepararEstoque</code> pode ser invocado pelo handler de <code>PedidoCriado</code>. </p>



<ul class="wp-block-list">
<li>Pode ser invocado por uma API de correção manual. </li>



<li>Pode ser invocado por um job de reprocessamento. </li>



<li>Pode ser invocado por um teste automatizado. </li>
</ul>



<p>O comando não carrega contexto de origem, ele expressa uma capacidade do serviço. <strong>E capacidades são, por definição, reutilizáveis.</strong></p>



<p>Quando alguém reclama que &#8220;precisou emitir o evento de novo porque era a única forma de disparar o fluxo&#8221;, o problema não é a arquitetura, o problema é que os comandos não foram desenhados como unidades independentes. O time construiu a integração inteira sobre eventos e não expôs os comandos como pontos de entrada autônomos.</p>



<h2 class="wp-block-heading">O que acontece quando você emite eventos que não representam fatos?</h2>



<p>Na teoria, a confusão entre evento e comando é um problema de modelagem. Na prática, as consequências são operacionais e caras.</p>



<p><strong>Rastreabilidade falsa.</strong> Se <code>PedidoCriado</code> é emitido tanto pela criação real de um pedido quanto por uma rotina de correção, o log do sistema registra dois eventos semanticamente idênticos que representam situações completamente diferentes. Qualquer análise, auditoria ou debugging precisa de contexto externo para distinguir qual é qual. A observabilidade perde valor.</p>



<p><strong>Efeitos colaterais incontroláveis.</strong> Cada handler que reage ao evento vai executar. Todos eles. <strong><span style="color: #ff0000;" class="stk-highlight">Se você emitiu <code>PedidoCriado</code> só porque precisava do handler de estoque, vai ter que lidar com a notificação indevida, a fatura duplicada, e qualquer outro handler que o time tenha plugado desde a última vez que alguém olhou o fluxo completo</span></strong>. A quantidade de handlers cresce ao longo do tempo, e cada novo handler é um efeito colateral potencial para quem emite o evento fora de contexto.</p>



<p><strong>Modelo mental corrompido.</strong> Quando o time percebe que eventos são emitidos sem que o fato correspondente tenha acontecido, a confiança no modelo se deteriora. <em><strong>Desenvolvedores começam a adicionar flags, campos de controle, condicionais dentro dos handlers para distinguir &#8220;evento real&#8221; de &#8220;evento forjado&#8221;. A complexidade acidental cresce até que ninguém confia na semântica de nenhum evento.</strong></em></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Antipattern" data-enlighter-group="exemplo-2">// Antipattern: forjando um evento para reaproveitar a cadeia
var eventoForjado = new PedidoCriado
{
    PedidoId = pedidoExistente.Id,
    // Campos preenchidos artificialmente
    // para disparar separação de estoque
};

await _bus.Publish(eventoForjado); 
// Todos os handlers vão reagir — inclusive os indesejados
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Correto" data-enlighter-group="exemplo-2">// Correto: invocar o comando diretamente
var comando = new SepararEstoque
{
    PedidoId = pedidoExistente.Id,
    Itens = pedidoExistente.Itens,
    Origem = OrigemSeparacao.CorrecaoManual
};

await _bus.Send(comando); 
// Apenas o serviço de estoque reage
</pre>



<p>A diferença entre os dois trechos não é estilística. No primeiro, você acionou uma cadeia inteira de efeitos colaterais sem intenção. No segundo, você expressou exatamente o que precisava, para quem precisava.</p>



<h2 class="wp-block-heading">A regra é simples, a disciplina é difícil</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Evento</td><td>Comando</td></tr><tr><td>Um evento registra que algo aconteceu</td><td>Um comando solicita que algo aconteça.</td></tr><tr><td>O handler de evento traduz fatos em solicitações.</td><td>Os comandos são os pontos de entrada reutilizáveis dos seus serviços.</td></tr></tbody></table></figure>



<p>Quando você sente a necessidade de emitir um evento que não corresponde a um fato real, isso não é um sinal de que a arquitetura está limitada. <strong>É um sinal de que os comandos do serviço destino não estão expostos como deveriam.</strong></p>



<p><strong><span style="color: #ff0000;" class="stk-highlight">O evento certo emitido pelo motivo errado causa mais dano do que o evento errado</span></strong>  porque passa pelos validadores, é aceito pelos handlers, e contamina o sistema inteiro enquanto parece perfeitamente correto nos logs.</p>The post <a href="https://gago.io/blog/event-driven-architecture-reaproveitamento-equivocado-eventos/">Event-driven Architecture: você está reaproveitando eventos ou mentindo para o seu próprio sistema?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/event-driven-architecture-reaproveitamento-equivocado-eventos/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>A corrida dos humanos</title>
		<link>https://gago.io/blog/corrida-dos-humanos/</link>
					<comments>https://gago.io/blog/corrida-dos-humanos/#comments</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Fri, 27 Feb 2026 14:38:18 +0000</pubDate>
				<category><![CDATA[Desenvolvimento]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20920</guid>

					<description><![CDATA[<p>Existe uma versão da corrida dos ratos que não vejo ninguém discutindo. Na versão original, a &#8220;corrida dos ratos&#8221; é um conceito popularizado por Robert Kiyosaki no livro Rich Dad Poor Dad. A metáfora descreve um ciclo em que a pessoa trabalha mais para ganhar mais, gasta mais porque ganha mais, e precisa trabalhar ainda [&#8230;]</p>
The post <a href="https://gago.io/blog/corrida-dos-humanos/">A corrida dos humanos</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Existe uma versão da corrida dos ratos que não vejo ninguém discutindo.</p>



<p>Na versão original, a &#8220;corrida dos ratos&#8221; é um conceito popularizado por Robert Kiyosaki no livro <em>Rich Dad Poor Dad</em>. A metáfora descreve um ciclo em que a pessoa trabalha mais para ganhar mais, gasta mais porque ganha mais, e precisa trabalhar ainda mais para sustentar o novo padrão de gastos. Exatamente como um rato correndo numa roda sem sair do lugar.</p>



<p>Esse conceito é a referência para o texto de hoje!</p>



<span id="more-20920"></span>



<p>Na original, descrita por Kiyosaki, o ciclo é financeiro: ganhar mais, gastar mais, precisar de mais. Na versão técnica, o ciclo é cognitivo: não saber, delegar para a IA, não conseguir avaliar, não aprender, saber menos.</p>



<p>O mecanismo é o mesmo. A roda gira, o esforço aumenta, e você não sai do lugar.</p>



<h2 class="wp-block-heading">O pedido que você não sabe fazer</h2>



<p>Você não consegue pedir o que não conhece. Se você nunca modelou um domínio complexo, não vai saber pedir para uma IA modelar. Se nunca debugou um deadlock em produção, não vai saber descrever o problema com precisão e clareza suficientes para receber uma resposta útil.</p>



<p>A qualidade do que a IA entrega é proporcional à profundidade do que você sabe perguntar.</p>



<h2 class="wp-block-heading">A avaliação que você não consegue fazer</h2>



<p>Pior que um código ruim é um código ruim que parece bom. Modelos geram soluções sintaticamente corretas e semanticamente perigosas com a mesma confiança. Se você não domina os detalhes, aceita. Coloca em produção. E descobre o custo semanas depois, meses depois, ou até anos depois, quando seu projeto enfim passa a receber atenção após muito esforço.</p>



<h2 class="wp-block-heading">O aprendizado que não acontece</h2>



<p>Aprender exige fazer, errar, corrigir, refazer. Quando você delega antes de aprender, elimina exatamente o ciclo que constrói competência.</p>



<p>Não existe atalho: ninguém aprende a nadar assistindo vídeos de natação.</p>



<p>E ninguém aprende arquitetura colando output de LLM.</p>



<h2 class="wp-block-heading">O ciclo vicioso</h2>



<p>Aqui está a armadilha:</p>



<ul class="wp-block-list has-medium-font-size">
<li>Você não sabe, então pede para a IA.</li>



<li>A IA entrega algo que você não consegue avaliar.</li>



<li>Você aceita sem entender.</li>



<li>Você não aprende.</li>



<li>Na próxima vez, sabe ainda menos.</li>
</ul>



<p>Cada iteração te torna mais dependente e menos capaz de identificar o que está errado.</p>



<p>A IA é uma ferramenta extraordinária para quem já construiu o repertório que permite usá-la com critério.</p>



<p>Para quem ainda não construiu, ela não é atalho.</p>



<p>É uma armadilha com interface amigável.</p>



<p class="has-text-align-center has-white-color has-text-color has-background has-link-color wp-elements-c479785366f31563a52a7a9f3810ba94" style="background:linear-gradient(135deg,rgb(67,98,86) 0%,rgb(25,151,105) 100%)">Antes de perguntar &#8220;<strong>como a IA pode fazer isso por mim</strong>&#8220;, <br />pergunte: &#8220;<strong>eu saberia avaliar se a resposta está errada?</strong>&#8220;</p>



<p>Se a resposta for não, o próximo passo não é um prompt melhor.</p>



<p>É estudar.</p>The post <a href="https://gago.io/blog/corrida-dos-humanos/">A corrida dos humanos</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/corrida-dos-humanos/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>OA03 &#8211; Fazemos o que precisa ser feito e arcamos com as consequências das decisões que tomamos</title>
		<link>https://gago.io/blog/oa03-fazer-o-que-precisa-ser-feito-sem-atalhos/</link>
					<comments>https://gago.io/blog/oa03-fazer-o-que-precisa-ser-feito-sem-atalhos/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Tue, 24 Feb 2026 03:26:20 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Desenvolvimento]]></category>
		<category><![CDATA[Oragon Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20881</guid>

					<description><![CDATA[<p>Seu time quer publicar pacotes NuGet mas não quer manter um NuGet Server. Quer rodar containers mas não quer contratar um Container Registry. Quer processar mensagens assíncronas mas acha que uma tabela com coluna status no PostgreSQL resolve. A pergunta que ninguém faz é: por que você está adotando uma solução cujo custo operacional mínimo [&#8230;]</p>
The post <a href="https://gago.io/blog/oa03-fazer-o-que-precisa-ser-feito-sem-atalhos/">OA03 – Fazemos o que precisa ser feito e arcamos com as consequências das decisões que tomamos</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Seu time quer publicar pacotes NuGet mas não quer manter um NuGet Server. Quer rodar containers mas não quer contratar um Container Registry. Quer processar mensagens assíncronas mas acha que uma tabela com coluna <code>status</code> no PostgreSQL resolve.</p>



<p>A pergunta que ninguém faz é: <strong>por que você está adotando uma solução cujo custo operacional mínimo você se recusa a pagar?</strong></p>



<span id="more-20881"></span>



<h2 class="wp-block-heading">O problema não é a ferramenta — é o subterfúgio</h2>



<p>Eu já perdi a conta de quantos projetos .NET eu avaliei onde a equipe adotou um conceito bom conceito, um bom padrão arquitetural, uma boa tecnologia, mas na hora de tornar real a implementação, optou por gambiarras que quebravam completamente o conceito original. </p>



<p>E os problemas são dos mais diversos, desde a :</p>



<ul class="wp-block-list">
<li>adoção de filas, mas em banco de dados.</li>



<li>adoção de serviços de identidade, mas abstraindo o serviço</li>



<li>adoção de cache, mas em banco ou em disco</li>



<li>adoção de NuGet para publicar pacotes, mas compartilhando pacotes em diretórios</li>



<li>adoção de docker ou kubernetes, mas sem querer adotar container registry</li>
</ul>



<p>Os padrões se repetem com uma previsibilidade deprimente:</p>



<p><strong>NuGet sem servidor</strong> — O time cria pacotes NuGet e distribui via pasta compartilhada, feed local, ou pior: copia DLLs entre repositórios. A consequência? Ninguém sabe qual versão está em produção. Não existe rastreabilidade. O pacote &#8220;compartilhado&#8221; vira um Frankenstein que cada projeto consome de um jeito.</p>



<p><strong>Containers sem registry</strong> — O time escreve Dockerfiles, builda imagens localmente, e faz deploy via <code>docker save</code> e <code>docker load</code>, ou publica direto no host. Não existe versionamento de imagens. Não existe rollback confiável. O &#8220;container&#8221; virou um zip quase sofisticado.</p>



<p><strong>Filas no banco de dados</strong> — O time precisa de processamento assíncrono, mas em vez de adotar RabbitMQ ou qualquer broker real, cria uma tabela <code>queue_messages</code> com polling a cada 5 segundos. O banco de dados, que já era o gargalo, agora também é o message broker. A fila não tem dead-letter, não tem retry com backoff, não tem observabilidade, não tem nada que uma fila de verdade oferece desde o primeiro segundo de operação.</p>



<p>Cada um desses cenários compartilha a mesma raiz: <strong>a recusa em assumir o custo operacional das decisões técnicas tomadas</strong>.</p>



<h2 class="wp-block-heading">O custo invisível do atalho</h2>



<p>Subterfúgios não são gratuitos. Eles parecem mais baratos no início porque transferem o custo do orçamento de infraestrutura para o orçamento de complexidade. E complexidade é a dívida mais cara que existe em software, porque ela cobra juros compostos em cada sprint.</p>



<p>Quando você evita um NuGet Server privado, você não economiza. </p>



<ul class="wp-block-list">
<li>Você paga com horas de debug tentando descobrir por que o projeto A usa a versão 2.1 do pacote e o projeto B usa a 2.3. </li>



<li>Você paga com o time tentando descobrir um comportamento que só existe na biblioteca, que não reflete o que existe no repositório, e descobre que determinada versão foi gerada na máquina do dev, mas o respectivo código não foi corretamente versionado.</li>



<li>Paga com builds quebrados porque alguém atualizou a DLL na pasta compartilhada sem avisar. </li>



<li>Paga com a impossibilidade de fazer rollback de um componente compartilhado para uma versão anterior.</li>
</ul>



<p>Quando você evita um Container Registry, </p>



<ul class="wp-block-list">
<li>você paga com deploys que não podem ser reproduzidos porque a imagem foi construída na máquina de alguém. </li>



<li>Paga com zero auditoria sobre o que está rodando em produção. </li>



<li>Paga com a incapacidade de escalar horizontalmente de forma confiável.</li>



<li>Paga com a inviabilidade de usar serviços profissionais e gerenciados para lidar com seu workload.</li>
</ul>



<p>Quando você substitui um message broker por uma tabela no banco, </p>



<ul class="wp-block-list">
<li>você paga com dead locks, contenção e degradação de performance no banco principal. </li>



<li>Paga com mensagens perdidas silenciosamente quando o polling falha. </li>



<li>Paga com um sistema de retry artesanal que nunca vai cobrir todos os edge cases que um broker dedicado já resolveu há décadas.</li>



<li>Paga com uma infinidade de conexões atolando o banco de dados, tornando-o quase inoperante.</li>



<li>Paga com a dependência.</li>
</ul>



<p><strong>A infraestrutura que você se recusa a pagar não desaparece. Ela reaparece como complexidade no seu dia-a-dia., na sua operação, mesmo que sequer tenha alcançado produção ainda.</strong></p>



<h2 class="wp-block-heading">Faça apenas uma vez, faça bem feito, faça de forma definitiva</h2>



<p class="has-white-color has-vivid-cyan-blue-to-vivid-purple-gradient-background has-text-color has-background has-link-color has-medium-font-size wp-elements-bf5a3766c4f8b68c14942e59be246679">As interpretações de ágil e o conceito de agilidade foram deturpados e usados pra criar justificativas pra a gambiarra. <br /><br />Para o profissional ruim, fazer apenas &#8220;o necessário&#8221; é o suficiente para &#8220;funcionar&#8221;. <br /><br />E assim se gastamos mais lidando com a gambiarra, do que fazendo do jeito certo uma só vez.</p>



<p>Se precisamos compartilhar código, criamos pacotes nuget.</p>



<p>Se precisamos criar pacotes NuGet, nós subimos na nossa infra ou contratamos como serviço um NuGet Server.</p>



<p>Se vamos trabalhar com containers, nós subimos na nossa infra ou contratamos como serviço um Container Registry. </p>



<p>Se precisamos de cache distribuído, nós adotamos Redis ou Dragonfly. </p>



<p>Se precisamos de filas, nós usamos RabbitMQ — não uma tabela no banco de dados.</p>



<p>Aí o zé gambiarra diz: &#8220;<em><strong>Mas isso adiciona complexidade operacional!</strong></em>&#8221; — Sim, adiciona. <br />E torço para que a escolha de um novo padrão, uma nova arquitetura ou uma nova tecnologia tenha sido feita por necessidade. </p>



<p>Pois não há adição de padrão, arquitetura ou tecnologia que não gere demanda adicional. Até porque é essa demanda adicional que, normalmente, é a solução para o problema que a tecnologia resolve.</p>



<p>A nova complexidade é <strong>explícita, documentada e gerenciável</strong> e principalmente: Conhecida. Não é novidade e não deveria ser surpresa. Ao ponto de facilmente poder ser adicionada a um requisito de vaga no LinkedIn. <br />Diferente da complexidade acidental que o subterfúgio adotado dentro de casa, ou melhor a gambiarra caseira criaria, que é invisível, imprevisível e cumulativa.</p>



<p>Mas adotar a ferramenta certa não significa aceitar burocracia desnecessária. Significa projetar o fluxo para que fazer o certo seja o caminho mais fácil.</p>



<h2 class="wp-block-heading">Na prática: NuGet + git submodules = Zero Burocracia</h2>



<p>Um dos cenários mais comuns é o desenvolvimento de componentes compartilhados via NuGet. </p>



<p>O fluxo <strong>ingênuo </strong>é: </p>



<ul class="wp-block-list">
<li><span style="color: #00990d;" class="stk-highlight">Alterar o código do componente </span></li>



<li><span style="color: #ff0000;" class="stk-highlight">Fazer commit e push para o repositório</span></li>



<li><span style="color: #ff0000;" class="stk-highlight">Esperar a pipeline rodar</span></li>



<li><span style="color: #ff0000;" class="stk-highlight">A pipeline publica no pacote no NuGet Server </span></li>



<li><span style="color: #ff0000;" class="stk-highlight">Atualizamos a referência no projeto consumidor </span></li>



<li><span style="color: #00990d;" class="stk-highlight">Finalmente podemos testar o novo código.</span></li>
</ul>



<p>4 passos adicionais entre codificar e testar uma mudança. </p>



<p>Isso definitivamente mata a produtividade e incentiva exatamente o tipo de atalho que queremos evitar.</p>



<p>A abordagem que eu uso combina <strong>git submodules</strong> com condicionais no <code>.csproj</code> para eliminar essa fricção sem abrir mão da governança:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;Project Sdk="Microsoft.NET.Sdk">

  &lt;PropertyGroup>
    &lt;TargetFramework>net9.0&lt;/TargetFramework>
  &lt;/PropertyGroup>

  &lt;!-- Em modo Debug, usa o código-fonte via submodule -->
  &lt;ItemGroup Condition="'$(Configuration)' == 'Debug'">
    &lt;ProjectReference Include="..\submodules\MeuComponente\src\MeuComponente.csproj" />
  &lt;/ItemGroup>

  &lt;!-- Em modo Release, exige o pacote publicado -->
  &lt;ItemGroup Condition="'$(Configuration)' == 'Release'">
    &lt;PackageReference Include="MeuComponente" Version="3.2.1" />
  &lt;/ItemGroup>

&lt;/Project>
</pre>



<p>O que esse setup garante:</p>



<p><strong>Durante o desenvolvimento (Debug)</strong>, o componente é referenciado como projeto via submodule. O desenvolvedor altera o código do componente, compila, testa e depura — tudo localmente, sem pipeline, sem espera, sem burocracia.</p>



<p><strong>No build oficial (Release)</strong>, o componente é consumido como pacote NuGet. Se a versão <code>3.2.1</code> não existir no NuGet Server, <strong>o build falha</strong>. Não existe deploy sem a versão correta publicada.</p>



<p>Essa dualidade é o que torna o fluxo sustentável. O desenvolvedor não é penalizado durante o desenvolvimento — o ciclo de feedback é instantâneo. A pipeline não aceita atalhos — o pacote precisa existir, versionado e publicado, para que o release funcione. A rastreabilidade é total — cada release aponta para uma versão específica de cada componente.</p>



<p>O git submodule funciona como um portal bidirecional: em debug, você enxerga e edita o código real. Em release, a referência de projeto simplesmente não existe — o build exige o pacote concreto.</p>



<p>Dica: Eu uso esse mesmo conceito para microsserviços&#8230;</p>



<h2 class="wp-block-heading">O princípio por trás da prática</h2>



<p>O padrão aqui não é sobre NuGet ou git submodules. É sobre um princípio de design de fluxo de trabalho:</p>



<p class="has-white-color has-vivid-cyan-blue-to-vivid-purple-gradient-background has-text-color has-background has-link-color has-medium-font-size wp-elements-c04e64718c4f0b8c71de2f5aabc75264"><strong>Fazer a coisa certa deve ser o caminho de menor resistência. </strong><br /><strong>Fazer a coisa errada deve ser impossível — ou pelo menos, visivelmente mais difícil.</strong></p>



<p>Quando você projeta o fluxo de forma que o build de release só funciona com o pacote publicado, você não precisa de documento de processo, não precisa de checklist, não precisa de code review focado em &#8220;será que ele atualizou a versão?&#8221;. O sistema impõe a regra.</p>



<p>Quando você dá ao desenvolvedor um submodule para iterar localmente, você remove o incentivo para copiar DLLs, referenciar pacotes locais, ou qualquer outra gambiarra que surge quando o caminho correto é burocrático demais.</p>



<h2 class="wp-block-heading">E para o resto da infraestrutura?</h2>



<p>O mesmo princípio se aplica a cada decisão operacional.</p>



<p><strong>Container Registry</strong> — Sim, tem custo. O GitHub Container Registry tem um tier gratuito generoso. O GitLab tem registry integrado. O Harbor é open-source e pode ser self-hosted. O Azure Container Registry tem um SKU básico. A desculpa de custo não sobrevive a 15 minutos de pesquisa.</p>



<p><strong>Message Broker</strong> — O RabbitMQ roda em um container com um <code>docker compose up</code>. O CloudAMQP tem tier gratuito para ambientes de desenvolvimento. Não existe cenário onde uma tabela com polling é mais fácil de operar do que um broker que já nasce com retry, dead-letter, exchange routing e management UI.</p>



<p><strong>Cache distribuído</strong> — O Redis também roda em um container. O Dragonfly é um drop-in replacement com melhor performance e menor consumo de memória. Ambos oferecem observabilidade nativa que qualquer implementação caseira levaria meses para atingir.</p>



<p>Nenhuma dessas soluções é gratuita em termos de operação. Mas a operação delas é <strong>conhecida, documentada e suportada por comunidades enormes</strong>. A operação da sua gambiarra é conhecida por uma pessoa — e essa pessoa vai sair da empresa em algum momento.</p>



<h2 class="wp-block-heading">Conclusão</h2>



<p>A próxima vez que alguém no seu time sugerir &#8220;a gente resolve isso com uma tabela no banco&#8221; ou &#8220;dá pra compartilhar o pacote por pasta&#8221;, faça uma pergunta simples:</p>



<p class="has-medium-font-size"><strong>Se a solução correta existe, é acessível e é documentada </strong><br /><strong>— por que estamos inventando uma versão pior?</strong></p>



<p class="has-vivid-red-color has-text-color has-link-color wp-elements-78c2e7e3896de20b676be9fd88b7efa4">A resposta quase sempre é uma combinação de preguiça operacional com medo de assumir responsabilidade por infraestrutura. E essas duas coisas, em um time que se propõe a trabalhar com sistemas distribuídos, são incompatíveis com o resultado que se espera entregar.</p>



<p class="has-medium-font-size">Faça o que precisa ser feito. <br />Pague o preço da decisão que você tomou <br />ou não tome a decisão alguma.</p>The post <a href="https://gago.io/blog/oa03-fazer-o-que-precisa-ser-feito-sem-atalhos/">OA03 – Fazemos o que precisa ser feito e arcamos com as consequências das decisões que tomamos</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/oa03-fazer-o-que-precisa-ser-feito-sem-atalhos/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>OA02 &#8211; Governança Arquitetural e Open Architecture- Você não pode esperar proatividade, muito menos de quem não tem acesso</title>
		<link>https://gago.io/blog/oa02-governanca-arquitetural-e-open-architecture/</link>
					<comments>https://gago.io/blog/oa02-governanca-arquitetural-e-open-architecture/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Tue, 17 Feb 2026 02:39:16 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Desenvolvimento]]></category>
		<category><![CDATA[Oragon Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20877</guid>

					<description><![CDATA[<p>As 7 regras que uso para governar componentes arquiteturais em projetos .NET de grande porte — e filtrar quem realmente está pronto para evoluir a arquitetura. Existe uma expectativa velada em muitos times de desenvolvimento: a de que bons desenvolvedores simplesmente &#8220;se envolvem&#8221; com a arquitetura. Que eles naturalmente vão questionar decisões, propor melhorias e [&#8230;]</p>
The post <a href="https://gago.io/blog/oa02-governanca-arquitetural-e-open-architecture/">OA02 – Governança Arquitetural e Open Architecture- Você não pode esperar proatividade, muito menos de quem não tem acesso</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p class="has-medium-font-size">As 7 regras que uso para governar componentes arquiteturais em projetos .NET de grande porte — e filtrar quem realmente está pronto para evoluir a arquitetura.</p>



<p>Existe uma expectativa velada em muitos times de desenvolvimento: a de que bons desenvolvedores simplesmente &#8220;se envolvem&#8221; com a arquitetura. Que eles naturalmente vão questionar decisões, propor melhorias e contribuir para a evolução técnica do projeto.</p>



<p>Mas aqui está o problema que quase ninguém está disposto a falar: como alguém pode ser proativo em relação a algo que não conhece, não enxerga e não tem acesso?</p>



<span id="more-20877"></span>



<p>Em boa parte dos projetos .NET de grande porte que já restruturei, encontrei o mesmo padrão no legado. Os componentes arquiteturais existiam, funcionavam e sustentavam o ecossistema — mas viviam encapsulados na cabeça de quem os criou. Sem documentação acessível, sem convenções claras de contribuição, sem um caminho visível para quem quisesse entender o porquê das decisões tomadas.</p>



<p>O resultado é previsível: desenvolvedores que poderiam contribuir ficam à margem. Não por falta de interesse ou capacidade, mas por falta de acesso. E quando algo dá errado, a narrativa é sempre a mesma — &#8220;o time não é proativo&#8221;, &#8220;ninguém se preocupa com arquitetura&#8221;, &#8220;está na cabeça do arquiteto que saiu&#8221;.</p>



<p>A verdade é mais incômoda: <strong>não é possível cobrar proatividade, muito menos de quem não tem acesso à arquitetura</strong>. Se os componentes que sustentam o projeto não estão disponíveis, organizados e com regras claras de evolução, o problema não está no desenvolvedor. Está na governança.</p>



<p>E foi exatamente para resolver isso que, ao longo dos anos, construí um conjunto de regras que me ajudam a conduzir projetos de grande porte. Regras que não apenas organizam componentes, mas criam um caminho claro para quem quer — e pode — contribuir com a arquitetura e infraestrutura de aplicação.</p>



<p>Antes de chegar nelas, porém, é preciso reconhecer algo que muitos líderes técnicos ignoram: nem todo desenvolvedor tem a mesma relação com a arquitetura. </p>



<p>E está tudo bem.</p>



<h2 class="wp-block-heading">Os 4 perfis de desenvolvedores diante da arquitetura</h2>



<p>Ao longo dos anos, percebi que desenvolvedores se posicionam diante da arquitetura de formas muito diferentes. Não é uma questão de senioridade no crachá — é uma combinação de interesse, capacidade e momento de carreira. Identifico 4 perfis recorrentes:</p>



<p><strong>Os que querem e podem.</strong> São desenvolvedores que têm interesse genuíno pela arquitetura e possuem a capacidade técnica para propor evoluções consistentes. Eles entendem trade-offs, conhecem o ecossistema e sabem que mudar um componente significa pensar em tudo que depende dele. Esses profissionais precisam de acesso e de um caminho formal para contribuir — sem isso, você os perde para a frustração ou para outra empresa.</p>



<p><strong>Os que querem, mas ainda não podem.</strong> Têm a curiosidade e a vontade, mas ainda não acumularam a experiência necessária para propor mudanças arquiteturais seguras. São os futuros arquitetos do seu time — se você criar as condições certas para que aprendam. O erro mais comum é ignorá-los ou, pior, dar a eles liberdade total sem guardrails. O resultado quase sempre é retrabalho.</p>



<p><strong>Os emocionados.</strong> Querem aprender, estão empolgados com a tecnologia, mas seu interesse é mais sobre absorver conhecimento do que sobre contribuir com mudanças. Não há nada de errado com isso. Eles são valiosos porque trazem energia e questionamentos que, muitas vezes, revelam pontos cegos na documentação ou na clareza dos componentes. Porém precisam de limites claros para não confundir entusiasmo com autonomia para alterar o que não compreendem por completo.</p>



<p><strong>Os que não se importam — desde que funcione.</strong> Para eles, a arquitetura é um meio, não um fim. Querem entregar funcionalidades, resolver problemas de negócio e seguir em frente. Esse perfil não é um problema — é uma realidade. Nem todo desenvolvedor precisa se tornar arquiteto. O que você precisa garantir é que os componentes arquiteturais simplifiquem o trabalho deles, sem exigir que entendam cada decisão por trás do design.</p>



<p>Esses 4 perfis coexistem na maioria dos times. O papel da governança arquitetural não é forçar todos a se tornarem arquitetos, mas criar um sistema que atenda a cada perfil de forma adequada: acesso para quem pode contribuir, guardrails para quem está aprendendo, clareza para quem só quer usar, e um filtro natural para separar entusiasmo de comprometimento real.</p>



<p>É exatamente isso que as 7 regras a seguir fazem.</p>



<h2 class="wp-block-heading">As 7 regras para governança de componentes arquiteturais</h2>



<p>Essas regras não surgiram de teoria. Elas nasceram de projetos reais, com times reais, enfrentando os mesmos problemas que provavelmente você enfrenta hoje: componentes duplicados, mudanças que quebram o ecossistema, desenvolvedores que refazem o que não entenderam e uma sensação constante de que a arquitetura está se fragmentando.</p>



<p>Organizei as regras em 3 blocos para facilitar a compreensão: acesso e simplificação, organização e qualidade, e responsabilidade ecossistêmica.</p>



<h3 class="wp-block-heading">Acesso e simplificação</h3>



<p><strong>Regra 1: os componentes arquiteturais precisam estar disponíveis para os desenvolvedores.</strong></p>



<p>Parece óbvio, mas não é. Em muitos projetos, os componentes existem como pacotes NuGet internos mal documentados, ou como código em repositórios que poucos sabem que existem, ou pior — como conhecimento tácito na cabeça de alguém que já saiu do time.</p>



<p>Se um componente não está disponível de forma clara — com documentação, exemplos de uso e um caminho para entender suas decisões de design — ele não existe para o time. E você não pode cobrar envolvimento com algo que, na prática, é invisível.</p>



<p>Disponibilizar não significa apenas publicar um pacote. Significa tornar visível o quê o componente faz, por quê ele existe, como deve ser usado e quais são seus limites. Sem isso, cada desenvolvedor vai criar sua própria solução — e aí começam as duplicações, as inconsistências e a erosão arquitetural silenciosa que só aparece quando já é tarde demais.</p>



<p><strong>Regra 2: os componentes arquiteturais devem simplificar o desenvolvimento e formalizar a forma de trabalho com determinadas tecnologias.</strong></p>



<p>Um componente arquitetural que adiciona complexidade é um componente que falhou no seu propósito. Se o desenvolvedor precisa entender 15 camadas de abstração para publicar uma mensagem no RabbitMQ ou para configurar um cache no Redis, algo está errado.</p>



<p>O papel do componente é encapsular decisões arquiteturais e transformá-las em APIs simples e coerentes. Ele deve formalizar a forma como o time trabalha com determinada tecnologia — estabelecendo padrões, convenções e limites — mas sem criar uma barreira de entrada que afaste justamente quem deveria usá-lo.</p>



<p>Na prática, o componente deve ser o caminho mais fácil para fazer a coisa certa. Se o desenvolvedor percebe que é mais simples ignorar o componente e implementar diretamente, você tem um problema de design, não de disciplina.</p>



<h3 class="wp-block-heading">Organização e qualidade</h3>



<p><strong>Regra 3: componentes são organizados em componentes locais (do projeto), regionais (compartilhados entre alguns projetos) e globais (da empresa).</strong></p>



<p>Essa distinção é fundamental e resolve um problema que poucos times enfrentam de forma consciente: <strong>nem todo componente tem o mesmo escopo de impacto.</strong></p>



<p>Componentes locais pertencem a um projeto específico. Eles encapsulam decisões que fazem sentido naquele contexto, para aquele domínio, com aquelas restrições. Seu raio de explosão é limitado — se algo der errado, o impacto é restrito ao projeto.</p>



<p>Componentes regionais são compartilhados entre um grupo de projetos que possuem afinidade técnica ou de domínio. Eles nascem quando dois ou mais projetos enfrentam o mesmo problema e a solução local se prova reutilizável — mas ainda não justifica uma adoção global. O regional é o nível mais traiçoeiro, porque vive no limbo entre a flexibilidade do local e a rigidez do global. Se não for governado com clareza, ele se torna um componente global disfarçado, sem o rigor que isso exige.</p>



<p>Componentes globais são compartilhados entre todos os projetos da empresa. Eles representam decisões arquiteturais que transcendem domínios específicos e afetam todo o ecossistema. Seu raio de explosão é proporcional ao número de consumidores — e, por definição, esse número é o maior possível.</p>



<p>Tratar os três da mesma forma é um erro. A exigência de qualidade, o processo de aprovação e a rigidez das validações precisam ser proporcionais ao impacto. E é exatamente isso que as duas regras seguintes estabelecem.</p>



<p><strong>Regra 4: componentes locais possuem validações de arquitetura flexibilizadas para o processo educacional de formar novos arquitetos.</strong></p>



<p>Componentes locais são o campo de treinamento. É aqui que os desenvolvedores dos perfis 2 e 3 — os que querem mas ainda não podem, e os emocionados — têm espaço para experimentar, errar e aprender.</p>



<p>Flexibilizar as validações não significa ausência de padrão. Significa que o custo de um erro é menor e que existe espaço para iteração. Um componente local pode ter uma abstração imperfeita, uma API que ainda não está no ponto ideal, um design que funciona mas não escala. Tudo isso é aceitável se o componente está contido no escopo do projeto e se serve como instrumento de aprendizado.</p>



<p>Esse é um ponto que muitos líderes técnicos erram: exigir o mesmo rigor de um componente local e de um componente global mata a iniciativa. O desenvolvedor que poderia estar aprendendo a pensar arquiteturalmente desiste porque o custo de contribuir é alto demais. E quando você perde essa janela de formação, perde futuros arquitetos.</p>



<p><strong>Regra 5: componentes regionais e globais possuem validações exaustivas de qualidade — proporcionais ao seu alcance.</strong></p>



<p>Se componentes locais são o campo de treinamento, componentes regionais e globais são produção. Aqui, o rigor é inegociável — mas a intensidade varia.</p>



<p>Um componente regional será consumido por um grupo conhecido de projetos. O processo de validação precisa ser rigoroso, mas o escopo de impacto é mapeável: você sabe quem são os consumidores, pode coordenar mudanças diretamente e o custo de uma migração é administrável. O rigor existe, mas com a vantagem de um raio de impacto controlado.</p>



<p>Um componente global vai ser consumido por todos os projetos da empresa, mantido por múltiplos times e vai sobreviver a múltiplas gerações de desenvolvedores. Ele precisa de testes exaustivos, documentação completa, versionamento rigoroso e um processo de revisão que garanta que cada mudança foi pensada no contexto do ecossistema inteiro. Aqui, o custo de um erro não é um projeto instável — é a confiança de toda a organização naquela fundação.</p>



<p>O padrão de qualidade mais alto não é burocracia — é proteção. Cada consumidor daquele componente confiou que ele se comportaria de determinada forma. Quebrar esse contrato sem critério é quebrar a confiança de todo o ecossistema.</p>



<h3 class="wp-block-heading">Responsabilidade ecossistêmica</h3>



<p><strong>Regra 6: componentes não podem sobrepor-se em funcionalidade.</strong></p>



<p>Sobreposição é o sintoma mais claro de governança ausente. Quando dois componentes fazem a mesma coisa de formas diferentes, o time perde a referência de qual é o caminho oficial. Cada desenvolvedor escolhe o que parece mais conveniente no momento, e a base de código se fragmenta.</p>



<p>Essa regra parece simples, mas sua aplicação exige disciplina constante. Toda vez que alguém propõe um novo componente, a primeira pergunta deve ser: isso já não existe? E se existe, por que não atende? Se a resposta for &#8220;porque eu não gosto de como foi feito&#8221;, voltamos ao próximo ponto — que é justamente sobre o que significa querer mudar algo que já existe.</p>



<p><strong>Regra 7: ao querer recriar ou modificar um componente, é preciso pensar nos demais consumidores. Não é permitido manter duas versões totalmente diferentes em produção.</strong></p>



<p>Essa é a regra mais exigente — e a mais importante. Ela estabelece que quem quer mudar um componente precisa assumir responsabilidade pelo ecossistema inteiro que depende dele.</p>



<p>Na prática, isso significa que subir uma nova versão de um componente global implica adaptar todos os projetos estáveis que o consomem. Não é permitido deixar a versão antiga rodando indefinidamente enquanto a nova segue em paralelo. Isso cria fragmentação, dificulta manutenção e gera inconsistências que se acumulam silenciosamente.</p>



<p>Essa regra é dura por design. Ela existe para filtrar mudanças impulsivas de mudanças fundamentadas. Se um desenvolvedor quer reescrever um componente, ele precisa primeiro entender por que o componente é como é — quais requisitos o moldaram, quais trade-offs foram aceitos, quais limitações foram deliberadas. Depois, precisa mapear todos os consumidores e planejar a migração.</p>



<p>Se, depois de tudo isso, ele ainda quer seguir em frente, é porque tem algo melhor a entregar. E esse dev merece livre passagem para entregar o seu melhor.</p>



<h2 class="wp-block-heading">O filtro natural</h2>



<p>Olhando para as 7 regras em conjunto, percebe-se que elas não são apenas governança técnica. Elas funcionam como um mecanismo de seleção natural.</p>



<p>Pense no desenvolvedor que quer modificar um componente global. Pelas regras, ele precisa:</p>



<ul class="wp-block-list">
<li>Entender todos os requisitos que levaram o componente a existir na forma atual.</li>



<li>Mapear todos os projetos e serviços que dependem dele.</li>



<li>Planejar uma migração que não quebre o ecossistema.</li>



<li>Garantir que sua proposta atende às validações exaustivas de qualidade.</li>
</ul>



<p>Cada uma dessas etapas é um filtro. Não um filtro burocrático, mas um filtro de comprometimento.</p>



<p>O desenvolvedor que desiste na primeira etapa aprendeu algo valioso: que o componente não é arbitrário, que existem razões concretas para cada decisão. Isso, por si só, já é uma vitória educacional — ele sai do processo entendendo o porquê, mesmo que não mude nada.</p>



<p>O desenvolvedor que desiste na segunda etapa descobriu a dimensão real do impacto. Percebeu que sua mudança não é isolada, que o ecossistema é maior do que o caso de uso que ele tinha em mente. Esse entendimento muda a forma como ele avalia mudanças dali em diante.</p>



<p>E o desenvolvedor que passa por todas as etapas e ainda quer seguir? Esse entendeu os requisitos, mapeou as dependências, planejou a migração e ainda assim está convencido de que tem algo melhor a entregar. Esse profissional não está agindo por impulso ou por preferência pessoal — está agindo por convicção técnica fundamentada.</p>



<p>Esse é o tipo de contribuição que evolui a arquitetura de verdade. <strong>E é exatamente o tipo de pessoa que as regras foram desenhadas para revelar.</strong></p>



<h2 class="wp-block-heading">Arquitetura aberta é arquitetura que evolui</h2>



<p>Governança arquitetural não é sobre restringir. É sobre criar as condições certas para que a arquitetura evolua de forma sustentável, com contribuições de quem realmente entende o peso do que está mudando.</p>



<p>Abrir a arquitetura — tornar componentes disponíveis, documentados e com regras claras — não significa perder controle. Significa criar um funil que desenvolve pessoas enquanto protege o ecossistema. Os curiosos aprendem, os comprometidos contribuem e os que só querem entregar funcionalidades têm um caminho simples para fazê-lo.</p>



<p>O primeiro passo é simples, mas exige honestidade: olhe para o seu projeto hoje e se pergunte — os componentes estão realmente acessíveis? Existe um caminho claro para quem quer contribuir? Ou você está cobrando proatividade de quem nunca teve acesso?</p>



<p>Se a resposta for incômoda, talvez seja hora de começar pelas regras.</p>The post <a href="https://gago.io/blog/oa02-governanca-arquitetural-e-open-architecture/">OA02 – Governança Arquitetural e Open Architecture- Você não pode esperar proatividade, muito menos de quem não tem acesso</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/oa02-governanca-arquitetural-e-open-architecture/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>OA01 &#8211; Por mais discriminadores de comportamento e menos enums</title>
		<link>https://gago.io/blog/oa01-por-mais-discriminadores-de-comportamento-e-menos-enums/</link>
					<comments>https://gago.io/blog/oa01-por-mais-discriminadores-de-comportamento-e-menos-enums/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Sun, 15 Feb 2026 09:07:49 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Desenvolvimento]]></category>
		<category><![CDATA[Oragon Architecture]]></category>
		<category><![CDATA[modeling]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20868</guid>

					<description><![CDATA[<p>Existe um vício oculto em projetos .NET que poucos questionam: usar enums para representar status, tipos e categorias, e depois espalhar if e switch por toda a base de código com base em deduções de regras implícitas. Esse código parece inofensivo. Está limpo, compila, passa no code review. Mas ele carrega um problema estrutural que [&#8230;]</p>
The post <a href="https://gago.io/blog/oa01-por-mais-discriminadores-de-comportamento-e-menos-enums/">OA01 – Por mais discriminadores de comportamento e menos enums</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Existe um vício oculto em projetos .NET que poucos questionam: usar enums para representar status, tipos e categorias, e depois espalhar <code>if</code> e <code>switch</code> por toda a base de código com base em deduções de regras implícitas.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if (grupo.Tipo == TipoGrupo.Administrador)
{
    // pode acessar tudo
}
else if (grupo.Tipo == TipoGrupo.Operador)
{
    // pode acessar quase tudo
}
else if (grupo.Tipo == TipoGrupo.Visualizador)
{
    // só leitura
}</pre>



<p>Esse código parece inofensivo. Está limpo, compila, passa no code review. Mas ele carrega um problema estrutural que escala de forma destrutiva: <strong>o comportamento do sistema está implícito no código, não explícito no modelo.</strong></p>



<p>Quem olha para o enum <code>TipoGrupo</code> não sabe quais permissões cada tipo carrega. Quem olha para o banco de dados não encontra essa informação. Quem precisa adicionar um novo tipo precisa caçar todos os <code>if</code> e <code>switch</code> espalhados pela aplicação para entender o que precisa implementar.</p>



<p>Isso não é modelagem. É arqueologia.</p>



<h2 class="wp-block-heading">o problema real: enums escondem especificações inteiras</h2>



<p>Um enum em C# é, na essência, um inteiro com um apelido, um nome. Ele não carrega semântica, não expressa capacidades, não declara limites. Toda a inteligência que deveria estar associada àquele valor vive em outro lugar — dispersa em condicionais, enterrada em services, duplicada entre controllers e workers.</p>



<p>Considere um sistema de pedidos com status:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public enum StatusPedido
{
    Rascunho,
    Confirmado,
    EmProcessamento,
    Enviado,
    Entregue,
    Cancelado
}
</pre>



<p>Parece completo. Mas esse enum não responde a nenhuma pergunta relevante sobre comportamento:</p>



<ul class="wp-block-list">
<li><strong>Quais status permitem cancelamento?</strong></li>



<li><strong>Em quais status o pedido pode ser editado?</strong></li>



<li><strong>Quais status geram notificação ao cliente?</strong></li>



<li><strong>Quais transições de status são válidas?</strong></li>
</ul>



<p class="has-white-color has-vivid-cyan-blue-to-vivid-purple-gradient-background has-text-color has-background has-link-color has-medium-font-size wp-elements-1e7ed23107a306e5c053a1a69c530fc4">Essas respostas estão espalhadas pelo código. E cada desenvolvedor que precisa dessa informação faz a mesma jornada: abre o repositório, busca por <code>StatusPedido</code>, lê dezenas de arquivos, monta mentalmente o mapa de comportamentos, e torce para não ter esquecido nenhum caso.</p>



<p>Quando um status novo precisa ser adicionado — digamos, <code>AguardandoRetirada</code> — o desenvolvedor adiciona o valor no enum e&#8230; começa a caçada. Cada <code>switch</code> precisa ser revisado. Cada <code>if</code> que compara com <code>StatusPedido.Enviado</code> pode precisar incluir o novo status. E se algum for esquecido, o bug não aparece na compilação. Aparece em produção.</p>



<p>Isso viola frontalmente o Open/Closed Principle: o sistema deveria ser aberto para extensão e fechado para modificação. Adicionar um novo tipo ou status não deveria exigir alteração de código existente. Mas com enums, <strong>cada novo valor é uma cirurgia em toda a base de código</strong>.</p>



<h2 class="wp-block-heading">Alternativa: Discriminadores de Comportamento</h2>



<p>A solução não é complexa. É uma mudança de perspectiva na modelagem: em vez de tratar status e tipos como constantes opacas, modelamos como <strong>entidades que declaram seus próprios comportamentos</strong>.</p>



<p>Em vez disso:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Tabela: Pedidos
- Id
- StatusPedidoId (int, FK → ?)
- ...

Enum no código: StatusPedido { Rascunho = 1, Confirmado = 2, ... }
</pre>



<p>Fazemos isso:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CREATE TABLE StatusPedido (
    Id INT PRIMARY KEY,
    Nome VARCHAR(50) NOT NULL,
    PermiteCancelamento BIT NOT NULL DEFAULT 0,
    PermiteEdicao BIT NOT NULL DEFAULT 0,
    NotificaCliente BIT NOT NULL DEFAULT 0,
    ExigeAprovacao BIT NOT NULL DEFAULT 0,
    Ativo BIT NOT NULL DEFAULT 1
);

INSERT INTO StatusPedido (Id, Nome, PermiteCancelamento, PermiteEdicao, NotificaCliente, ExigeAprovacao)
VALUES
    (1, 'Rascunho',          1, 1, 0, 0),
    (2, 'Confirmado',        1, 0, 1, 0),
    (3, 'Em Processamento',  0, 0, 0, 0),
    (4, 'Enviado',           0, 0, 1, 0),
    (5, 'Entregue',          0, 0, 1, 0),
    (6, 'Cancelado',         0, 0, 1, 0);
</pre>



<p>Agora cada status declara explicitamente o que permite e o que não permite. Não existe dedução. Não existe interpretação. A especificação está no dado, não no código.</p>



<h2 class="wp-block-heading">O mesmo princípio para tipos: flags de comportamento</h2>



<p>Voltando ao exemplo do tipo de grupo. Em vez de perguntar &#8220;este grupo é do tipo Administrador?&#8221;, perguntamos &#8220;este tipo de grupo possui a capacidade de administração?&#8221;:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CREATE TABLE TipoGrupo (
    Id INT PRIMARY KEY,
    Nome VARCHAR(50) NOT NULL,
    Administrador BIT NOT NULL DEFAULT 0,
    PodeGerenciarUsuarios BIT NOT NULL DEFAULT 0,
    PodeAcessarRelatorios BIT NOT NULL DEFAULT 0,
    PodeAlterarConfiguracoes BIT NOT NULL DEFAULT 0
);

INSERT INTO TipoGrupo (Id, Nome, Administrador, PodeGerenciarUsuarios, PodeAcessarRelatorios, PodeAlterarConfiguracoes)
VALUES
    (1, 'Administrador', 1, 1, 1, 1),
    (2, 'Operador',      0, 0, 1, 1),
    (3, 'Visualizador',  0, 0, 1, 0),
    (4, 'Suporte',       0, 1, 1, 0);
</pre>



<p>A diferença é sutil na estrutura, mas profunda no impacto. Agora, quando o sistema precisa saber se um grupo pode gerenciar usuários, a pergunta é direta:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">if (grupo.Tipo.PodeGerenciarUsuarios)
{
    // comportamento permitido
}
</pre>



<p>Não estamos mais perguntando <strong>quem</strong> o grupo é. Estamos perguntando <strong>o que</strong> o grupo pode fazer. Essa distinção elimina a necessidade de conhecer a lista de tipos para implementar lógica. O código se torna agnóstico ao tipo específico e reage às capacidades declaradas.</p>



<h2 class="wp-block-heading">Quem se beneficia dessa abordagem?</h2>



<p>Esse princípio deq ue &#8220;comportamentos não podem ser deduzidos&#8221; se aplica a qualquer entidade que carrega regras implícitas avaliadas por condicionais no código. Além de status e tipos, os candidatos mais óbvios:</p>



<p><strong>Perfis e papéis (roles)</strong> — o clássico <code>if (usuario.Role == "Admin")</code> espalhado por dezenas de controllers. Flags como <code>PodeAprovar</code>, <code>PodeExcluir</code>, <code>AcessaRelatorios</code> tornam as capacidades do papel explícitas e extensíveis sem tocar em código.</p>



<p><strong>Categorias de produto/serviço</strong> — quando a categoria define comportamento fiscal, logístico ou de precificação. Em vez de <code>if (produto.Categoria == Categoria.Digital)</code> para pular cálculo de frete, uma flag <code>ExigeFrete</code> na tabela de categorias.</p>



<p><strong>Planos e níveis de assinatura (tiers)</strong> — <code>if (plano == Plano.Premium)</code> para liberar funcionalidades. Flags como <code>PermiteExportacao</code>, <code>LimiteUsuarios</code>, <code>SuportePrioritario</code> tornam cada plano autodescritivo.</p>



<p><strong>Modalidades de pagamento</strong> — em vez de deduzir que boleto exige compensação assíncrona e cartão é síncrono, flags como <code>CompensacaoAssincrona</code>, <code>PermiteEstorno</code>, <code>ExigeConfirmacao</code>.</p>



<p><strong>Tipos de documento/contrato</strong> — quando o tipo define fluxo de aprovação, validade, necessidade de assinatura digital. Flags como <code>ExigeAssinatura</code>, <code>TemValidade</code>, <code>RequerAprovacao</code>.</p>



<p><strong>Severidades e prioridades (em sistemas de incidentes, tickets, alertas)</strong> — em vez de <code>if (severidade == Severidade.Critica)</code> para decidir se notifica por SMS, flags como <code>NotificaSMS</code>, <code>EscalaAutomaticamente</code>, <code>BloqueiaDeploy</code>.</p>



<p><strong>Etapas de pipeline/workflow</strong> — qualquer sistema com fluxo de etapas onde cada etapa habilita ou bloqueia ações. Flags como <code>PermiteRetrocesso</code>, <code>ExigeAnexo</code>, <code>GeraTarefa</code>.</p>



<p><strong>Tipos de evento em event-driven</strong> — quando o tipo do evento determina se ele exige retry, se é idempotente, se deve ir para dead letter após falha. Flags como <code>Idempotente</code>, <code>RetryAutomatico</code>, <code>CriticoParaConsistencia</code>.</p>



<p>O padrão é sempre o mesmo: se existe um <code>if</code> no código que avalia a identidade da entidade para deduzir um comportamento, esse comportamento deveria ser um discriminador explícito na tabela que define aquela entidade. A pergunta não é &#8220;quem é&#8221;, é &#8220;o que declara que pode fazer&#8221;.</p>



<h2 class="wp-block-heading">Antes e Depois com C#</h2>



<h3 class="wp-block-heading">Antes: comportamento implícito, acoplado ao valor do enum</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class PedidoService
{
    public async Task CancelarAsync(Pedido pedido)
    {
        // quem decidiu que esses status permitem cancelamento?
        // onde está essa especificação?
        // se um novo status surgir, quem vai lembrar de atualizar aqui?
        if (pedido.Status != StatusPedido.Rascunho &amp;&amp; 
            pedido.Status != StatusPedido.Confirmado)
        {
            throw new InvalidOperationException(
                "Pedido não pode ser cancelado no status atual.");
        }

        pedido.Status = StatusPedido.Cancelado;
        await _repository.AtualizarAsync(pedido);
    }

    public async Task EditarAsync(Pedido pedido, DadosPedido dados)
    {
        // mesma lógica espalhada em outro lugar
        if (pedido.Status != StatusPedido.Rascunho)
        {
            throw new InvalidOperationException(
                "Pedido não pode ser editado no status atual.");
        }

        pedido.Aplicar(dados);
        await _repository.AtualizarAsync(pedido);
    }

    public async Task NotificarClienteAsync(Pedido pedido)
    {
        // e aqui mais uma lista de status que "alguém sabe" que notifica
        if (pedido.Status == StatusPedido.Confirmado ||
            pedido.Status == StatusPedido.Enviado ||
            pedido.Status == StatusPedido.Entregue ||
            pedido.Status == StatusPedido.Cancelado)
        {
            await _notificacaoService.EnviarAsync(pedido);
        }
    }
}
</pre>



<p>Três métodos, três listas de status hardcoded, zero rastreabilidade. Se um status novo for adicionado ao enum, o compilador não reclama. O sistema simplesmente se comporta de forma errada — silenciosamente.</p>



<h3 class="wp-block-heading">Depois: comportamento declarativo, dirigido por dados</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class StatusPedidoEntity
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public bool PermiteCancelamento { get; set; }
    public bool PermiteEdicao { get; set; }
    public bool NotificaCliente { get; set; }
    public bool ExigeAprovacao { get; set; }
}
</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class PedidoService
{
    public async Task CancelarAsync(Pedido pedido)
    {
        if (!pedido.Status.PermiteCancelamento)
        {
            throw new InvalidOperationException(
                $"Status '{pedido.Status.Nome}' não permite cancelamento.");
        }

        pedido.Status = await _statusRepository
            .ObterPorNomeAsync("Cancelado");
        await _repository.AtualizarAsync(pedido);
    }

    public async Task EditarAsync(Pedido pedido, DadosPedido dados)
    {
        if (!pedido.Status.PermiteEdicao)
        {
            throw new InvalidOperationException(
                $"Status '{pedido.Status.Nome}' não permite edição.");
        }

        pedido.Aplicar(dados);
        await _repository.AtualizarAsync(pedido);
    }

    public async Task NotificarClienteAsync(Pedido pedido)
    {
        if (pedido.Status.NotificaCliente)
        {
            await _notificacaoService.EnviarAsync(pedido);
        }
    }
}
</pre>



<p>O código encolheu, ficou mais legível e — o mais importante — <strong>parou de mentir</strong>. Não existem mais listas secretas de status que permitem ou proíbem ações. A verdade está na tabela. O código apenas pergunta.</p>



<h2 class="wp-block-heading">O impacto em escala: da UI ao worker</h2>



<p>Quando comportamentos estão declarados em dados, o benefício não se limita ao backend. Ele permeia toda a aplicação:</p>



<p><strong>Na API</strong>, o endpoint que retorna um pedido pode incluir as capacidades do status atual. O frontend não precisa replicar lógica — recebe <code>permiteCancelamento: true</code> e renderiza ou esconde o botão.</p>



<p><strong>No worker</strong>, o consumidor que processa eventos pode consultar flags do status para decidir se executa uma ação, sem carregar regras hardcoded que precisam ser sincronizadas com o backend.</p>



<p><strong>Na documentação</strong>, a tabela de status com suas flags é, por si só, uma especificação funcional. Um product owner consegue ler a tabela e validar se os comportamentos estão corretos — sem ler código.</p>



<p><strong>No onboarding</strong>, um desenvolvedor novo consulta uma tabela e entende imediatamente quais comportamentos cada status habilita. Não precisa fazer arqueologia em dezenas de arquivos para montar o mapa mental.</p>



<h2 class="wp-block-heading">transições de status: o próximo passo natural</h2>



<p>Se status declaram comportamentos, o passo seguinte é declarar também as transições válidas:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CREATE TABLE TransicaoStatusPedido (
    StatusOrigemId INT NOT NULL,
    StatusDestinoId INT NOT NULL,
    ExigeAprovacao BIT NOT NULL DEFAULT 0,
    PRIMARY KEY (StatusOrigemId, StatusDestinoId),
    FOREIGN KEY (StatusOrigemId) REFERENCES StatusPedido(Id),
    FOREIGN KEY (StatusDestinoId) REFERENCES StatusPedido(Id)
);

INSERT INTO TransicaoStatusPedido (StatusOrigemId, StatusDestinoId, ExigeAprovacao)
VALUES
    (1, 2, 0),  -- Rascunho → Confirmado
    (2, 3, 0),  -- Confirmado → Em Processamento
    (3, 4, 0),  -- Em Processamento → Enviado
    (4, 5, 0),  -- Enviado → Entregue
    (1, 6, 0),  -- Rascunho → Cancelado
    (2, 6, 1);  -- Confirmado → Cancelado (exige aprovação)
</pre>



<p>Agora a máquina de estados inteira é declarativa. Validar uma transição no código se reduz a uma consulta:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public async Task TransicionarAsync(Pedido pedido, int novoStatusId)
{
    var transicao = await _transicaoRepository
        .ObterAsync(pedido.Status.Id, novoStatusId);

    if (transicao is null)
    {
        throw new InvalidOperationException(
            $"Transição de '{pedido.Status.Nome}' para o status " + 
            $"destino não é permitida.");
    }

    if (transicao.ExigeAprovacao)
    {
        await _aprovacaoService.SolicitarAsync(pedido, novoStatusId);
        return;
    }

    pedido.Status = await _statusRepository.ObterAsync(novoStatusId);
    await _repository.AtualizarAsync(pedido);
}
</pre>



<p>Adicionar um novo status? Insere na tabela e define as transições. Zero alteração no código. Open/Closed honrado, não na teoria.</p>



<h2 class="wp-block-heading">É para abolir o uso de Enums? Quando o enum ainda faz sentido</h2>



<p>Seria desonesto dizer que enums não têm lugar. Eles são adequados quando o valor é genuinamente estático, sem comportamento associado e sem expectativa de extensão: dias da semana, unidades de medida, direções cardeais. Valores que são constantes universais, não regras de negócio disfarçadas.</p>



<p>O problema não é o enum como estrutura de dados. É o uso do enum como <strong>veículo de especificação implícita</strong>. Quando cada valor carrega consigo um conjunto de regras que só existem nos <code>if</code> do código, o enum deixou de ser uma constante e virou uma especificação escondida.</p>



<h2 class="wp-block-heading">Checklist: como identificar enums que deveriam ser tabelas? </h2>



<p>Se pelo menos uma das condições abaixo for verdadeira, o enum é candidato a ser promovido para tabela com flags de comportamento:</p>



<ul class="wp-block-list">
<li>Existe pelo menos um <code>if</code> ou <code>switch</code> no código que avalia o valor do enum para decidir um comportamento.</li>



<li>Adicionar um novo valor ao enum exige alteração em mais de um arquivo.</li>



<li>O significado de cada valor do enum precisa ser explicado verbalmente para novos desenvolvedores — não é autoevidente.</li>



<li>O mesmo enum é avaliado em camadas diferentes da aplicação (API, serviço, worker) com lógicas que precisam estar sincronizadas.</li>



<li>O product owner ou analista de negócio não consegue validar as regras associadas ao enum sem ler código.</li>
</ul>



<p>Se três ou mais dessas condições são verdadeiras, não é uma questão de preferência. É débito técnico ativo.</p>



<h2 class="wp-block-heading">Conclusão</h2>



<p>Enums em C# são uma ferramenta de representação, não de modelagem. Quando usamos enums para carregar regras de negócio, estamos escolhendo conveniência no momento da escrita em troca de obscuridade permanente na manutenção.</p>



<p>Cada <code>if (entidade.Status == Status.XPTO)</code> é uma regra de negócio que existe apenas na memória de quem escreveu aquele código. Quando essa pessoa sai do time, a regra vira folclore. Quando o sistema cresce, o folclore vira risco operacional.</p>



<p>Promover status e tipos para tabelas com comportamento declarado não é overengineering. É tornar explícito o que já existe — só que escondido nos lugares errados.</p>



<p>A pergunta que todo arquiteto deveria fazer ao revisar um modelo de domínio é simples: <strong>se eu apagar todo o código e olhar apenas para o banco de dados, consigo entender o que o sistema permite e o que ele proíbe?</strong></p>



<p>Se a resposta é não, os dados estão incompletos. E nenhuma quantidade de <code>if</code> no código compensa um modelo que não se explica sozinho.</p>The post <a href="https://gago.io/blog/oa01-por-mais-discriminadores-de-comportamento-e-menos-enums/">OA01 – Por mais discriminadores de comportamento e menos enums</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/oa01-por-mais-discriminadores-de-comportamento-e-menos-enums/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Oragon.RabbitMQ 1.6 &#8211; Welcome .NET 10</title>
		<link>https://gago.io/blog/oragon-rabbitmq-1-6/</link>
					<comments>https://gago.io/blog/oragon-rabbitmq-1-6/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Thu, 12 Feb 2026 04:36:28 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[.NET de A a Z]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Oragon Architecture]]></category>
		<category><![CDATA[RabbitMQ de A a Z]]></category>
		<category><![CDATA[RabbitMQ para Aplicações .NET]]></category>
		<category><![CDATA[System Design]]></category>
		<category><![CDATA[The Microservices Journey]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20847</guid>

					<description><![CDATA[<p>Desde a versão 1.1.0 (janeiro/2025), o Oragon.RabbitMQ passou por 6 releases com mais de 70 commits, resultando em +3.894 linhas adicionadas e -1.245 removidas ao longo de 112 arquivos. A seguir, um resumo das principais novidades. O que mudou de v1.1 para v1.6 Suporte a .NET 10 A partir da v1.6.0, o projeto passou a [&#8230;]</p>
The post <a href="https://gago.io/blog/oragon-rabbitmq-1-6/">Oragon.RabbitMQ 1.6 – Welcome .NET 10</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Desde a versão 1.1.0 (janeiro/2025), o Oragon.RabbitMQ passou por 6 releases com mais de 70 commits, resultando em +3.894 linhas adicionadas e -1.245 removidas ao longo de 112 arquivos. </p>



<p>A seguir, um resumo das principais novidades.</p>



<span id="more-20847"></span>



<h2 class="wp-block-heading">O que mudou de v1.1 para v1.6</h2>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Suporte a .NET 10</h3>



<p>A partir da v1.6.0, o projeto passou a ter suporte nativo a três gerações do .NET simultaneamente:</p>



<ul class="wp-block-list">
<li>.NET 8 (LTS)</li>



<li>.NET 9</li>



<li>.NET 10 (adicionado na v1.6.0) Isso garante que equipes em diferentes estágios de adoção possam utilizar a biblioteca sem restrições.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Novos Padrões de Mensageria: ReplyResult e ForwardResult (Estáveis)</h3>



<p>Introduzidos como experimentais na v1.4.0 e promovidos a estáveis na v1.6.0, esses dois novos IAmqpResult ampliam significativamente os padrões de mensageria suportados:</p>



<h3 class="wp-block-heading">ReplyResult &#8211; Padrão RPC (Request-Reply)</h3>



<p>Permite que um handler responda diretamente ao remetente da mensagem, habilitando o padrão RPC de forma nativa:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.MapQueue("rpc-queue", (MyRequest request) =>
  {
      var response = ProcessRequest(request);
      return AmqpResults.ReplyAndAck(response);
  });</pre>



<ul class="wp-block-list">
<li>Extrai automaticamente o ReplyTo da mensagem original</li>



<li>Preserva correlação via CorrelationId / MessageId</li>



<li>Cria um canal dedicado para a resposta, evitando race conditions </li>
</ul>



<h3 class="wp-block-heading">ForwardResult &#8211; Encaminhamento de Mensagens Permite redirecionar mensagens </h3>



<p>processadas para outros exchanges/filas:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.MapQueue("intake-queue", (Order order) =>
  {
      var enrichedOrder = Enrich(order);
      return AmqpResults.ForwardAndAck("orders-exchange", "processed", mandatory: true, enrichedOrder);
  });</pre>



<ul class="wp-block-list">
<li>Suporta envio para exchanges com routing key</li>



<li>Flag mandatory para garantia de entrega</li>



<li>Parâmetro opcional replyTo para encadeamento de replies</li>
</ul>



<h3 class="wp-block-heading">ComposableResult &#8211; Composição de Ações</h3>



<p>Permite encadear múltiplas ações AMQP em um único retorno de handler:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">app.MapQueue("my-queue", (MyMessage msg) =>
  {
      return AmqpResults.Compose(
          AmqpResults.Reply(response),
          AmqpResults.Forward("audit-exchange", "audit.key", false, msg),
          AmqpResults.Ack()
      );
  });</pre>



<p>Métodos auxiliares como AmqpResults.ReplyAndAck() e AmqpResults.ForwardAndAck() simplificam os casos mais comuns.</p>



<h3 class="wp-block-heading">Topology Initializer &#8211; Declaração Programática de Topologia</h3>



<p>Adicionado na v1.5.0 (originalmente como ChannelInitializer, renomeado na v1.5.2 para maior clareza), permite declarar exchanges, filas e bindings antes do consumer começar a consumir:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">  app.MapQueue("my-queue", handler)
     .WithTopology(async (channel, ct) =>
     {
         await channel.ExchangeDeclareAsync("my-exchange", "topic", cancellationToken: ct);
         await channel.QueueBindAsync("my-queue", "my-exchange", "routing.key", cancellationToken: ct);
     });</pre>



<p>Isso elimina a necessidade de configuração externa de topologia e garante que a infraestrutura esteja pronta antes do consumo iniciar.</p>



<h3 class="wp-block-heading">Otimizações de Performance: Expression Trees</h3>



<p>A v1.6.0 trouxe duas otimizações significativas que eliminam o uso de Reflection no hot path:</p>



<h4 class="wp-block-heading">Dispatcher com Expression-based Invoker</h4>



<p>O Dispatcher substituiu DynamicInvoke (reflection) por delegates compilados via System.Linq.Expressions. O delegate é compilado uma única vez na inicialização e reutilizado para todos os dispatches de mensagem:</p>



<p>Antes: handler.DynamicInvoke(args) // reflection em cada mensagem<br />Depois: compiledInvoker(args) // delegate compilado, zero reflection</p>



<h4 class="wp-block-heading">TaskOfAmqpResultResultHandler com Expression-based Extractor</h4>



<p>A extração de Task.Result também foi migrada de PropertyInfo.GetValue para Expression Trees compiladas.</p>



<p>Resultado nos benchmarks: O overhead do Oragon sobre o consumer nativo do RabbitMQ.Client é de apenas ~1-5% em throughput, com ratio ~1.00 em cenários de I/O bound e ~1.02-1.07 em cenários CPU bound.</p>



<h3 class="wp-block-heading">Eventos de Conexão: Shutdown, Blocked e Unblocked</h3>



<p>Novos event handlers no QueueConsumer oferecem visibilidade sobre o estado da conexão:</p>



<ul class="wp-block-list">
<li>ConnectionShutdownAsync (Critical): Loga quando a conexão é perdida, indicando que o consumer não se auto-recupera</li>



<li>ConnectionBlockedAsync (Warning): Detecta quando o RabbitMQ está sob pressão de recursos (memória/disco)</li>



<li>ConnectionUnblockedAsync (Information): Indica que a pressão de recursos foi resolvida.</li>
</ul>



<p>Todos utilizam o padrão de alta performance LoggerMessage.Define com event IDs estruturados.</p>



<h3 class="wp-block-heading">Health Checks Integrados</h3>



<p>Integração nativa com o sistema de Health Checks do ASP.NET Core, especialmente útil com .NET Aspire:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">builder.AddRabbitMQClient("rabbitmq"); // Health check registrado automaticamente</pre>



<ul class="wp-block-list">
<li>Registra health checks automaticamente ao configurar a conexão</li>



<li>Suporte a conexões keyed e non-keyed</li>



<li>Pode ser desabilitado via settings.DisableHealthChecks = true</li>



<li>Degradação graciosa: se a conexão falhar durante o registro, usa um FailedHealthCheck wrapper</li>
</ul>



<h3 class="wp-block-heading">IAsyncDisposable e Gerenciamento de Recursos</h3>



<p>A gestão de ciclo de vida foi modernizada:</p>



<ul class="wp-block-list">
<li>ConsumerServer: Removido IDisposable síncrono, mantendo apenas IAsyncDisposable</li>



<li>QueueConsumer: Dispose assíncrono completo com cancelamento de tokens, desregistro de event handlers e disposal do channel</li>



<li>Consumers são descartados em ordem reversa durante o shutdown</li>



<li>Proteção contra double-disposal com flag disposedValue</li>
</ul>



<h4 class="wp-block-heading">Melhorias de Thread-Safety e Async</h4>



<ul class="wp-block-list">
<li>Uso correto e consistente de ConfigureAwait em toda a stack</li>



<li>Campo isLocked no ConsumerDescriptor tornado volatile para thread-safety</li>



<li>Propagação adequada de CancellationToken em toda a cadeia assíncrona</li>



<li>Criação de conexões totalmente assíncrona</li>
</ul>



<h4 class="wp-block-heading">Logging de Alta Performance</h4>



<ul class="wp-block-list">
<li>Adoção do LoggerMessage.Define para alta performance</li>



<li>Logging estruturado com Event IDs em todos os caminhos críticos</li>



<li>Log para mensagens deserializadas como null (EventId 12)</li>



<li>Informações diagnósticas detalhadas em eventos de conexão</li>
</ul>



<h3 class="wp-block-heading">Cobertura de Testes Ampliada</h3>



<p>Novos testes unitários adicionados para:</p>



<ul class="wp-block-list">
<li>ConsumerDescriptor (+518 linhas)</li>



<li>QueueConsumer (+525 linhas)</li>



<li>ConsumerServer (+439 linhas)</li>



<li>ForwardResult (+196 linhas)</li>



<li>AsStringExtensions, NewReverseList, e mais</li>
</ul>



<p>Nossas métricas no sonar estão bem interessantes também:</p>



<figure class="wp-block-image size-large"><img data-dominant-color="fbfbfc" data-has-transparency="true" style="--dominant-color: #fbfbfc;" loading="lazy" decoding="async" width="1024" height="738" src="https://gago.io/wp-content/uploads/2026/02/image-1-1024x738.png" alt="" class="wp-image-20856 has-transparency" srcset="https://gago.io/wp-content/uploads/2026/02/image-1-1024x738.png 1024w, https://gago.io/wp-content/uploads/2026/02/image-1-980x706.png 980w, https://gago.io/wp-content/uploads/2026/02/image-1-480x346.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<h3 class="wp-block-heading">Benchmarks &#8211; A cereja do bolo</h3>



<p>A v1.6.0 inclui um projeto de benchmarks (Oragon.RabbitMQ.Benchmarks) com cenários que medem:</p>



<ul class="wp-block-list">
<li>Throughput: Comparação direta Native vs Oragon</li>



<li>Latência: Overhead por mensagem</li>



<li>Alocações: Memória alocada por operação</li>



<li>Escalabilidade de Concorrência: Comportamento com diferentes níveis de prefetch e dispatch concurrency</li>



<li>RPC: Performance do padrão request-reply Os resultados confirmam que o Oragon mantém performance virtualmente idêntica ao consumer nativo em cenários I/O bound (ratio ~1.00).</li>
</ul>



<h2 class="wp-block-heading">Cronologia</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Versão</th><th>Data</th><th>Destaques</th></tr></thead><tbody><tr><td>1.1.0</td><td>Jan/2025</td><td>Baseline documentada no post anterior</td></tr><tr><td>1.2.x-beta</td><td>Abr/2025</td><td>Testes de ConsumerServer, renaming para ConsumerDescriptor</td></tr><tr><td>1.3.0-beta</td><td>Abr/2025</td><td>ReplyAndAck, ComposableResult</td></tr><tr><td>1.4.0</td><td>Jun/2025</td><td>ForwardResult (experimental), melhorias de locking</td></tr><tr><td>1.5.0</td><td>Set/2025</td><td>Topology Initializer, Health Checks, melhorias de conexão</td></tr><tr><td>1.5.1</td><td>Set/2025</td><td>Refactoring da inicialização do QueueConsumer</td></tr><tr><td>1.5.2</td><td>Set/2025</td><td>Rename para TopologyInitializer</td></tr><tr><td>1.6.0</td><td>Fev/2026</td><td>.NET 10, Expression Trees, eventos de conexão, ReplyResult/ForwardResult estáveis, benchmarks</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">O mais importante</h2>



<p>O mais importante acompanhando projetos usando o <strong>Oragon.RabbitMQ</strong> é a capacidade de entregar um fluxo padronizado e uniforme, sem precisar tocar em tão baixo nível no AMQP/RabbitMQ.Client, permitindo consumir filas como se fossem minimal API&#8217;s.</p>



<p>Ao eliminar o boilerplate, adotando uma política rígida de ack, nack e reject que não suje nem mascare a realidade no dashboard e métricas do RabbitMQ, torna a vida mais fácil, mais simples.</p>



<h2 class="wp-block-heading">Nem tudo são flores</h2>



<p>Um erro grave no fluxo de inicialização dos consumidores, produzia um fire and forget no start do consumer, mascarando erros que fossem desse início da jornada. Cenário não tão comum, mas fácil de acontecer quando você está &#8220;mudando tudo&#8221; (exchanges, binds, filas etc) que era um dos cenários de cliente.</p>



<h2 class="wp-block-heading">Novas demandas</h2>



<p>Nesse momento, tenho uma demanda exótica que consiste em uma fila de controle usada para receber nomes de filas que precisam ser atendidas por uma janela limitada de tempo, como uma fila de atenção, uma implementação similar à que sistema operacional usa para que seu processador consiga atender mais processos e threads do que cores disponíveis. </p>



<p>Para o Oragon.RabbitMQ a demanda é permitir subir e parar dinamicamente o consumo de uma fila, trocar a fila alvo e continuar o ciclo.</p>



<p class="has-medium-font-size"><a href="https://github.com/luizcarlosfaria/Oragon.RabbitMQ" target="_blank" rel="noopener" title="">https://github.com/luizcarlosfaria/Oragon.RabbitMQ</a></p>The post <a href="https://gago.io/blog/oragon-rabbitmq-1-6/">Oragon.RabbitMQ 1.6 – Welcome .NET 10</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/oragon-rabbitmq-1-6/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Agnostic Services, Hexagonal Architecture SEM DDD</title>
		<link>https://gago.io/blog/agnostic-services-hexagonal-architecture-sem-ddd/</link>
					<comments>https://gago.io/blog/agnostic-services-hexagonal-architecture-sem-ddd/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Sun, 28 Dec 2025 18:31:56 +0000</pubDate>
				<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[System Design]]></category>
		<category><![CDATA[Software Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20827</guid>

					<description><![CDATA[<p>Esta semana, me deparei com críticas à arquitetura Hexagonal (Ports and Adapters) fundamentadas na suposição equivocada de que seu uso está condicionado à aplicação de Domain-Driven Design (DDD). Como se fosse um privilégio reservado apenas a sistemas que alcançaram um suposto &#8220;grau de maturidade arquitetural&#8221;. Essa ideia, infelizmente, afasta bons profissionais de algumas das abordagens [&#8230;]</p>
The post <a href="https://gago.io/blog/agnostic-services-hexagonal-architecture-sem-ddd/">Agnostic Services, Hexagonal Architecture SEM DDD</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Esta semana, me deparei com críticas à arquitetura Hexagonal (Ports and Adapters) fundamentadas na suposição equivocada de que seu uso está condicionado à aplicação de Domain-Driven Design (DDD). Como se fosse um privilégio reservado apenas a sistemas que alcançaram um suposto &#8220;grau de maturidade arquitetural&#8221;. Essa ideia, infelizmente, afasta bons profissionais de algumas das abordagens mais poderosas para evolutividade e manutenção de software.</p>



<p>Separar seu núcleo de negócio das camadas tecnológicas não é sobre a adoção de DDD, embora possa tirar proveito de&#8230;</p>



<span id="more-20827"></span>



<p>É sobre <strong>clareza</strong>, <strong>fluidez </strong>e <strong>inteligência de arquitetura</strong>. Ao isolar o <strong>core</strong> da aplicação — <em><strong>e uso &#8220;core&#8221; propositalmente para não usar o termo &#8220;domínio&#8221;</strong></em> — você está tomando uma decisão consciente de <strong>reduzir acoplamento</strong>, <strong>minimizar a carga cognitiva</strong> e <strong>facilitar a evolução</strong> do sistema.</p>



<p>Hexagonal não é sobre a possibilidade de trocar tecnologias como banco de dados ou frameworks web. É sobre <strong><span style="color: #ff0000;" class="stk-highlight">não misturar alhos com bugalhos</span></strong>. </p>



<p><strong>Quando se mantém o coração da aplicação alheio às tecnologias de entrada e saída, cria-se um terreno sólido para sustentação e adaptação futuras.</strong> Embora não pareça intuitivo, reduz a complexidade.</p>



<h2 class="wp-block-heading">Não é sobre DDD, é sobre boas práticas fundamentais</h2>



<p>Um equívoco recorrente é presumir que a adoção de uma arquitetura hexagonal ou de serviços agnósticos exige obrigatoriamente a implementação de DDD (Domain-Driven Design). Embora DDD e Hexagonal compartilhem ideais como separação de responsabilidades e foco no domínio de negócio, <strong>não há dependência entre eles</strong>. A arquitetura hexagonal pode (e deve) ser usada em qualquer cenário que exija <strong>organização</strong>, <strong>desacoplamento </strong>e <strong>manutenibilidade</strong>, <span style="color: #ff0000;" class="stk-highlight">independentemente da complexidade ou da presença de um domínio rico</span>.</p>



<p>É totalmente possível — e frequentemente desejável — aplicar uma arquitetura limpa e desacoplada mesmo em sistemas com domínio simples ou centrados em orquestração. Você não precisa de agregados, entidades ricas ou repositórios complexos para justificar a escolha por Port and Adapters ou Agnostic Services. </p>



<p>Basta querer:</p>



<ul class="wp-block-list">
<li>evoluir com liberdade</li>



<li>testar com confiança</li>



<li>manter com tranquilidade.</li>
</ul>



<figure class="wp-block-pullquote"><blockquote><p><em>Ao insistir que DDD seja pré-requisito para boas práticas arquiteturais, limitamos seu uso a uma elite de projetos &#8220;dignos&#8221;. </em><br /><em>Isso não apenas uma falácia, como também desincentiva equipes de investir em qualidade desde cedo. </em><br /><em>Arquiteturas que promovam a separação clara de responsabilidades são <strong>ferramentas universais</strong>. </em><br /><em>Elas pertencem a todos os projetos que desejam crescer de forma saudável.</em></p></blockquote></figure>



<h2 class="wp-block-heading">Ser manutenível exige esforço</h2>



<p>Projetos que começam simples, com APIs REST sincrônicas, muitas vezes evoluem para demandas mais sofisticadas: <strong>cache, filas, streams, resiliência, escalabilidade</strong>. A transição natural de um fluxo sincrônico para um modelo assíncrono acontece muito antes de se atingir milhões de requisições por segundo. <span style="color: #fe0000;" class="stk-highlight"><strong>A necessidade de resiliência surge cedo, independente do tamanho do projeto</strong></span>. E é comum que nesses cenários, projetos fortemente acoplados e dependentes da camada web se tornem verdadeiros gargalos para evolução. Refatorar controladores que fazem tudo, reorganizar lógicas de negócio espalhadas entre middlewares, services e endpoints passa a ser uma tarefa onerosa e arriscada, dificultando a introdução de novas portas de entrada como filas ou streams.</p>



<p>E é justamente aqui que a arquitetura mostra mais uma vez seu valor. Se o seu core está amarrado diretamente à camada web, a introdução de novos mecanismos de comunicação exige refatoração pesada, arriscada e cara. Agora, se esse core for agnóstico — desacoplado, limpo e bem definido — <strong>novas portas</strong> podem ser conectadas com mínimo atrito, com o mínimo esforço.</p>



<p>Projetos bem pensados não são sobre previsão do futuro, mas sobre <strong>preparação inteligente para o inesperado</strong>. Como uma bicicleta com rodas encaixadas, não soldadas: você não sabe se vai precisar trocá-las, mas se precisar, não precisa descartar a bicicleta inteira.</p>



<p>E se você acha que esse é o argumento central, calma! O melhor está por vir.</p>



<h2 class="wp-block-heading">Arquitetura não é luxo, é inteligência</h2>



<p>Existe um preconceito comum contra a arquitetura hexagonal, como se fosse um <strong><span style="color: #ff0000;" class="stk-highlight">“enlatado complexo” reservado a sistemas elitizados</span></strong>.</p>



<figure class="wp-block-pullquote"><blockquote><p>A verdade é que por trás do desenho conceitual está um princípio fundamental da engenharia de software: <strong>separação clara de responsabilidades</strong>. <br />Não se trata de overengineering, trata-se de manter coesão, facilitar testes, e principalmente, tornar a manutenção mais leve e o crescimento mais natural.</p></blockquote></figure>



<p>Antes de rejeitar uma abordagem por parecer sofisticada demais, reflita: </p>



<ul class="wp-block-list">
<li>quantas horas você ou seu time gastam para adaptar o sistema a uma nova necessidade? </li>



<li>Quantos efeitos colaterais emergem de mudanças aparentemente simples? </li>
</ul>



<p>E se a solução não for mais complexidade, mas <strong>mais organização e inteligência na separação de responsabilidades</strong>?</p>



<h2 class="wp-block-heading">Agnostic Services criou uma nova era na forma como modelo software</h2>



<p>Desde 2006, utilizo o conceito de <strong>Agnostic Services</strong> como base para construção de sistemas e posso afirmar, sem exageros, que essa decisão transformou profundamente minha maneira como enxergo, desenvolvo e entrego software.</p>



<p>A ideia central é simples: construir serviços que <strong>não conhecem o meio de entrada</strong> e <strong>não dependem da camada de transporte</strong>. São componentes de negócio autocontidos, testáveis, reaproveitáveis, que podem ser invocados por qualquer &#8220;porta&#8221;: seja uma API REST, uma fila RabbitMQ, um worker em background, uma requisição gRPC ou um job schedulado. Esse modelo traz uma liberdade arquitetural imensa e elimina a fricção para expansão.</p>



<p>Ao adotar Agnostic Services, passei a pensar e evoluir sistemas de forma mais estratégica. A entrega de novas features tornou-se mais fluida, e a capacidade de reagir às demandas se multiplicou, além de reduzir drasticamente a quantidade de bugs introduzidos por mudanças estruturais. Os times com quem trabalhei reclamaram muito, e reclamam até hoje, mas conseguiram escalar (aumentar quantidade de features) de suas soluções com menos dor e mais confiança.</p>



<p>Essa escolha, feita há quase duas décadas, moldou toda a minha abordagem profissional. E se hoje consigo construir soluções resilientes, adaptáveis e sustentáveis, muito disso se deve a essa base: <strong>serviços agnósticos e foco em separação real de responsabilidades</strong>.</p>



<h2 class="wp-block-heading">Hexagonal e Agnostic Services: convergência de princípios</h2>



<p>Ao observar com atenção, percebemos que a arquitetura Hexagonal e os Agnostic Services compartilham a mesma essência: <strong><span style="color: #ff0000;" class="stk-highlight">manter o núcleo de negócio isolado das preocupações externas</span></strong>. Ambas as abordagens valorizam a independência do core em relação às tecnologias de transporte, infraestrutura ou comunicação.</p>



<p>A principal diferença está na perspectiva e na abrangência de suas propostas.</p>



<p>A arquitetura Hexagonal define uma estrutura arquitetural clara, e se aprofunda nos conceitos de portas e adaptadores delimitando as interações entre o mundo externo e o sistema. Enquanto Agnostic Services refere-se a serviços projetados para serem completamente independentes do meio de transporte, infraestrutura ou plataforma.</p>



<p>Enquanto Hexagonal fala de todo o isolamento completo do core, Agnostic Services se limita à preocupação em não contaminar os serviços com especificidades de tecnologias de exposição e seus protocolos, como Web API, HTTP, REST, gRPC, AMQP, RESP. O transporte é tratado como um detalhe de configuração externa.</p>



<p>Se por um lado a arquitetura Hexagonal define os limites do sistema, tanto em portas, quanto em adapters e como as integrações se conectam a ele, os Agnostic Services se preocupam apenas com as portas e a <strong>pureza e coesão das unidades internas</strong>. </p>



<p>Portanto, se você já adota arquitetura hexagonal, é provável que já faça uso de Agnostic Services, mas caso não, pode ser um passo menor, e talvez o próximo passo natural. E se você já pratica Agnostic Services, a arquitetura Hexagonal oferece um arcabouço que amplifica ainda mais seus benefícios.</p>



<h2 class="wp-block-heading"><strong>Mas por que citar Agnostic Services em uma publicação cujo foco é Hexagonal e DDD?</strong> </h2>



<p>A similaridade entre ambas as abordagens é enorme, partem dos mesmos princípios e são formas mais simples ou mais complexas de lidar com o mesmo problema: <strong>acoplamento entre chamada de negócio e tecnologias</strong> diversas. Esse acoplamento torna, muitas vezes, o negócio imperceptível dentro de fluxos basicamente tecnológicos.</p>



<p>A mistura entre regras de negócio a burocracia de componentes tecnológicos multiplicam a complexidade, dificultando a manutenção e aumenta principalmente a probabilidade de surgirem erros durante manutenções.</p>



<p>E aqui está meu ponto sobre o assunto: Embora tenha praticado pouco Hexagonal, pratico Agnostic Services há ao menos 18 anos, e suas semelhanças me permitem transplantar ideias de um universo para o outro, obviamente mantendo-me atento às diferenças.</p>



<h2 class="wp-block-heading">Uma escolha que define o futuro</h2>



<p>Arquiteturas que separam o [CORE, DOMÍNIO, NEGÓCIO] de tecnologias específicas, simplificam a evolução e geram diferencial na hora de manter, seja para mudar ou evoluir.</p>



<figure class="wp-block-pullquote"><blockquote><p>Adotar uma arquitetura que promova um core agnóstico é metade do caminho para evitarmos que um projeto seja condenado a ser reescrito na hora de evoluir sua a arquitetura.</p></blockquote></figure>



<p>Você encontrará essas pistas em todos os meus projetos desde 2006, e o que ganhei com essa abordagem:</p>



<ul class="wp-block-list">
<li>Desafetos</li>



<li>Stress</li>



<li>Perda de Cabelo</li>



<li>mas também:
<ul class="wp-block-list">
<li>Projetos mais bem delimitados.</li>



<li>Errar feio, é algo burocrático (precisa fazer bastante força).</li>



<li>Decisões difíceis, que demandariam muito esforço, passam a ser triviais.</li>
</ul>
</li>
</ul>



<p>Evoluções e restruturações de projetos sempre foi desafiador para clientes e consultores, entretanto em todos os casos, segui abordagens minimalistas que visavam consumir o mínimo de infraestrutura, para entregar profissionalismo aos projetos restruturados.</p>



<p>Meu PlayBook de refatoração conta com:</p>



<ul class="wp-block-list">
<li>observabilidade (comecei com ELK Stack, mas hoje privilegio LGTM)</li>



<li>Cache (redis)</li>



<li>Filas (rabbitmq) </li>
</ul>



<p>esses elementos estão no topo das primeiras necessidades de todos os projetos que já fui chamado para restruturar. E não tenho históricos de fracassos ou grandes desafios em projetos de restruturação com esse PlayBook.</p>



<p>Mas o grande problema é que, na maior parte das vezes, aplicações centradas na web, em especial em Web Apis ou Web Apps, sem um core agnóstico, demandam muita reescrita e muita rearquitetura para permitir a adoção de filas, ou o uso de outros protocolos. O acoplamento cobra um preço muito alto.</p>



<p>Quando escolhi promover Agnostic Services à parte da baseline de arquitetura para meus projetos, (<strong><span style="color: #ff0000;" class="stk-highlight">ou seja, exceto caso seja algo muito exótico (ainda não aconteceu), ele estará presente na arquitetura de qualquer projeto meu</span></strong>), ganhei projetos que ofereciam maior clareza, cada coisa no seu devido lugar.</p>



<p>A capacidade de reagir às necessidades cotidianas aumentou, e agora lidar com incertezas deixou de ser um grande problema estrutural. Mover fluxos que precisam de mais resiliência do que o oferecido pelo HTTP, saiu de uma escala de semanas ou meses, para uma escala de horas ou dias. Tudo porque ficou mais simples.</p>



<p>A separação rígida melhorou a testabilidade, aumentou o reaproveitamento de componentes, o que fortalece DRY, e reduziu a dificuldade de manter os projetos.</p>



<p><span style="color: #ff0000;" class="stk-highlight">Mas o maior benefício, do ponto de vista de arquitetura, está em não precisar fazer falsas suposições hipotéticas. Não precisar chutar números com base em predições mediúnicas geradas a partir de números inventados.</span></p>



<p>Agora eu posso dizer um belo e singelo: <span style="color: #ff0000;" class="stk-highlight"><strong>Não sei e não me importa</strong>.</span><br />Porque agora eu posso dizer: <strong><span style="color: #ff0000;" class="stk-highlight">Independente do que venha, reagimos rápido e antes que se torne um problema de fato!</span></strong></p>



<p>Essa abordagem me permite reduzir a importância da decisão sobre filas ou http síncrono. No final das contas, &#8220;tanto faz&#8221; e, portanto, entrega a flexibilidade que eu preciso para não precisar tomar essa decisão e morrer abraçado a ela.</p>



<p>Muitas vezes só queremos não correr muito risco, e não precisar tomar decisões das quais não temos informações suficientes para uma previsão informada, só suposições com alta margem de erro.</p>



<p>Agnostic Services tem sido meu &#8220;seguro&#8221; contra incertezas, previsões erradas e suposições equivocadas. E Hexagonal Architecture/Ports and Adapters, pode herdar o mesmo status nos seus projetos, independente da adoção de DDD ou não.</p>The post <a href="https://gago.io/blog/agnostic-services-hexagonal-architecture-sem-ddd/">Agnostic Services, Hexagonal Architecture SEM DDD</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/agnostic-services-hexagonal-architecture-sem-ddd/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>RabbitMQ: Filas efêmeras</title>
		<link>https://gago.io/blog/rabbitmq-filas-efemeras/</link>
					<comments>https://gago.io/blog/rabbitmq-filas-efemeras/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Tue, 19 Aug 2025 06:39:38 +0000</pubDate>
				<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[RabbitMQ de A a Z]]></category>
		<category><![CDATA[RabbitMQ para Aplicações .NET]]></category>
		<category><![CDATA[RabbitMQ]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20777</guid>

					<description><![CDATA[<p>Ao pensar em filas, é comum pensarmos em filas com um ciclo de vida muito longo, filas que existem enquanto a aplicação existir. É comum pensarmos em filas como recursos estáticos que fazem parte dos requisitos de funcionamento da aplicação, nascendo quando a aplicação é implantada em produção pela primeira vez e somente deixando de [&#8230;]</p>
The post <a href="https://gago.io/blog/rabbitmq-filas-efemeras/">RabbitMQ: Filas efêmeras</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Ao pensar em filas, é comum pensarmos em filas com um ciclo de vida muito longo, filas que existem enquanto a aplicação existir. </p>



<p>É comum pensarmos em filas como recursos estáticos que fazem parte dos requisitos de funcionamento da aplicação, nascendo quando a aplicação é implantada em produção pela primeira vez e somente deixando de existir somente quando a aplicação é desativada ou substituída.</p>



<p>Hoje vamos abordar filas que possuem um ciclo de vida diferente, um ciclo de vida absolutamente curto, filas que podem durar de poucos milissegundos e semanas, e eventualmente até meses ou anos.</p>



<p>Não adianta torcer o nariz. Muitos aplicativos que estão no teu celular hoje fazem uso desse recurso em seus backends.</p>



<span id="more-20777"></span>



<p>Desde 2013 usando e falando de RabbitMQ, ainda percebo certo incômodo quando falamos de filas não tradicionais.</p>



<p>Antes de avançarmos no ponto central do texto, preciso pontuar os tipos de filas, para entendermos exatamente do que estamos falando.</p>



<h2 class="wp-block-heading">Tipos de Filas</h2>



<p>No RabbitMQ temos 3 tipos de filas:</p>



<ul class="wp-block-list">
<li>Classic Queues</li>



<li>Quorum Queues</li>



<li>Streams*</li>
</ul>



<p class="has-text-align-right has-small-font-size">Stream não é exatamente um tipo de fila, mas é apresentada no RabbitMQ como se fosse. <br />Seria um preciosismo desnecessário isolar streams, mas é importante entender que não são filas.</p>



<h3 class="wp-block-heading">Classic queues</h3>



<p>Classic queues são o tipo tradicional do RabbitMQ, compatíveis com todo o ecossistema (exchanges, DLX, TTL, plugins) e otimizadas para latência baixa e compatibilidade com clientes .NET (RabbitMQ.Client). Elas suportam filas duráveis e mirrors (federation/HA policies), mas o modelo de espelhamento clássico pode causar reordenação e penalidades em failover; são ideais para workloads gerais, baixa complexidade operacional e onde throughput moderado e integração imediata com features existentes são prioritários.</p>



<h3 class="wp-block-heading">Quorum queues</h3>



<p>Quorum queues são filas replicadas no cluster baseadas em Raft projetadas para alta disponibilidade e consistência forte, substituindo as mirrored queues para cargas críticas: tolerância a falhas mais previsível, menor risco de perda de mensagens e comportamento de eleição determinístico. Elas sacrificam um pouco de latência/throughput em comparação às classic queues em prol de durabilidade e segurança de dados — recomendadas para mensagens de missão crítica, billing, comandos financeiros ou onde a ordem e a persistência são requisitos firmes; funcionam bem com clientes .NET e suportam DLX/TTL, mas dimensionamento e tuning são necessários.</p>



<h3 class="wp-block-heading">Streams</h3>



<p>RabbitMQ Streams oferece um modelo baseado em log de alta performance e retenção longa, pensado para grandes volumes, leitura sequencial e replay (sem a limitação de consumo único por fila), aproximando-se de sistemas estilo Kafka. Streams garantem alto throughput, ordenação de partição e offset-based consumption, sendo ideais quando você precisa de retenção histórica, replay/consumer-groups e processamento de eventos em larga escala; possuem cliente diferenciado, mas podem usar a mesma estrutura de consumo AMQP existente e demandam arquitetura e operações diferentes (storage/retention tuning), por isso são a escolha certa quando ordenação, replay e escala são primordiais.</p>



<p>RabbitMQ Streams é uma estrutura de dados replicada persistente que pode realizar as mesmas tarefas que as filas: elas armazenam em buffer mensagens de produtores que são lidas por consumidores. No entanto, os fluxos diferem das filas em dois aspectos importantes: </p>



<ul class="wp-block-list">
<li>Como as mensagens são armazenadas</li>



<li>Como as mensagens são consumidas.</li>
</ul>



<h2 class="wp-block-heading">Filas de Longa duração &#8211; O ciclo de vida padrão</h2>



<p>O ciclo de vida mais comum adotado para filas no RabbitMQ é o de longa duração, que consiste em filas que existem durante toda a vida da aplicação.</p>



<p class="has-text-align-center has-small-font-size">Esse é o tipo de fila mais usado, mais comum, e é o que você precisa conhecer primeiro. <br />Os demais ciclos de vida são secundários.</p>



<p>Esse tipo de ciclo de vida das filas é típico em sistemas de todos os tipos, não há demérito nem perdas em sua utilização. Ele é o mais usado, pois atende aos mais vastos casos de uso, além de ser o ciclo de vida mais simples, mais fácil de ser implementado e o primeiro a ser ensinado.</p>



<h2 class="wp-block-heading">Filas efêmeras</h2>



<p>Filas de longa duração são ótimas para a maioria dos workloads. Entretanto, uma fila com múltiplos consumidores não permite determinar/forçar o envio para um consumidor específico. Não temos a capacidade de determinar para qual consumidor uma mensagem deve ser entregue. </p>



<p>E há um número substancial de casos de uso em que se faz necessário fazer esse roteamento avançado.</p>



<h3 class="wp-block-heading">IoT</h3>



<p>Em um cenário de IoT o dispositivo geralmente envia mensagens para uma fila de longa duração, entretanto, para receber mensagens, o dispositivo precisa ter uma fila própria.</p>



<p>Essa fila pode durar enquanto o dispositivo existir, sobrevivendo aos restarts do dispositivo, ou pode durar apenas enquanto o dispositivo estiver conectado ao RabbitMQ.</p>



<p>Ambas as abordagens são válidas e possuem prós e contras.</p>



<h4 class="wp-block-heading">RPC</h4>



<p>Em um fluxo RPC (Request / Response) que use filas, temos a demanda de criação de uma fila a cada request. Quem recebe esse request, em geral, são filas de longa duração, mas o Response é entregue em uma fila que foi criada dinamicamente e essa fila dura apenas alguns milissegundos, poucos segundos ou poucos minutos.</p>



<p>Para implementar RPC você precisa seguir regras estritas e rígidas, aqui exemplifico o server dessa relação:</p>



<ol class="wp-block-list">
<li>Uma fila de longa duração é previamente criada.</li>



<li>Um ou múltiplos consumidores começam a consumir essa fila para atender os requests que chegarem.</li>



<li>Esses consumidores são implementados de forma que, além do body, também farão a leitura dos cabeçalhos AMQP da mensagem em busca do cabeçalho ReplyTo. Esse cabeçalho informa para o consumidor, qual o endereço AMQP para envio do Response.</li>



<li>Cada Request recebido, o consumidor processará a mensagem e, ao final, enviará o Response para o endereço AMQP contido na propriedade ReplyTo do Request.</li>
</ol>



<p>Para esse fluxo ganhar vida o client dessa relação tem obrigações também:</p>



<ol class="wp-block-list">
<li>Antes de enviar o Request, o client deve solicitar ao RabbitMQ uma fila anônima que será responsável por receber o Response.</li>



<li>O RabbitMQ deve retornar uma fila com um nome.</li>



<li>Antes de publicar a mensagem na fila do server, o cliente deve adicionar um cabeçalho ReplyTo na mensagem, informando qual o endereço AMQP da fila de resposta.</li>



<li>O cliente precisa parar a thread em que está em execução e aguardar o Response na fila anônima, consumindo essa fila.</li>



<li>Após um timeout ou a chegada da resposta, a fila anônima deve ser apagada, e o processamento que estava parado deve continuar com a resposta recebida.</li>
</ol>



<p>Eu já abordei RPC aqui em &#8220;RPC sob AMQP seduz enquanto mata… sua implantação de mensageria&#8221;</p>



<p><blockquote class="wp-embedded-content" data-secret="I0CjJyUkuD"><a href="https://gago.io/blog/rabbitmq-rpc-cantodasereia/">RPC sob AMQP seduz enquanto mata&#8230; sua implantação de mensageria</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;RPC sob AMQP seduz enquanto mata&#8230; sua implantação de mensageria&#8221; &#8212; gaGO.io" src="https://gago.io/blog/rabbitmq-rpc-cantodasereia/embed/#?secret=jYFyrLoXze#?secret=I0CjJyUkuD" data-secret="I0CjJyUkuD" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe></p>



<h4 class="wp-block-heading">RPC vs Two Way Async</h4>



<p class="has-small-font-size">A obrigatoriedade de aguardar a resposta para prosseguir com o processamento, bloqueando a thread, é característico do fluxo RPC, tornando-o semelhante a um Request HTTP, só que obviamente sob AMQP, usando filas.</p>



<p>O motivo para um Request HTTP não ser resiliente, independente de termos ou não a capacidade de retentativa, se dá por conta de não conseguirmos tolerar um downtime significativo. Em um fluxo que faça um Request HTTP temos um estado de memória, em uma thread, e em caso de falha nesse processo, temos algum tempo para recuperar a operação, mas esse tempo não é indefinido.</p>



<p>Uma desconexão de um usuário, um crash do processo, uma falha de rede, qualquer coisa que faça a thread morrer, leva consigo aquele request. Uma queda de luz, um simples deploy, uma reorganização de instâncias da aplicação são tarefas capazes de tombar a thread que originou o Request que estava em andamento, fazendo-o se perder. </p>



<p class="has-text-align-right has-small-font-size">Aliás, um dos meios de entregar resiliência para esse <br />fluxo consiste em englobá-lo em um consumo de fila.</p>



<p>O ponto é que o fluxo RPC não é resiliente porque precisa do Response na mesma thread que fez o Request, e eventualmente essa thread pode não existir mais, perdendo o estado anterior e talvez sequer recebendo a resposta.</p>



<p>Da mesma forma que um Request HTTP não é resiliente, por conta da necessidade de aguardar uma resposta síncrona, o fluxo de RPC AMQP também não é resiliente pelo mesmo motivo.</p>



<p>Então, sempre que possível, evite RPC.</p>



<p class="has-small-font-size">Em muitos cenários não precisamos realmente aguardar essa resposta na mesma thread que originou o Request. Podemos, inclusive, quebrar o fluxo em duas etapas: </p>



<ul class="wp-block-list">
<li>na primeira, o processo termina com o envio do pedido</li>



<li>na segunda, ele é retomado apenas quando a resposta chega. </li>
</ul>



<p>Nesse modelo, em vez de um fluxo <strong>Request/Response</strong>, temos dois <strong>Requests</strong> independentes, que se conectam de forma assíncrona.</p>



<p><strong>Esse é um fluxo resiliente!</strong></p>



<p class="has-small-font-size">Esse fluxo é baseado em 2 filas de longa duração, o que o torna mais fácil, mais eficiente e de quebra: Resiliente. </p>



<p class="has-medium-font-size">É importante evitar a adoção de RPC, porque fluxos RPC não são resilientes.</p>



<p class="has-small-font-size">RPC só ganha resiliência, com a adoção de um garantidor que possa englobar a operação toda, como a adoção de filas.</p>



<p class="has-small-font-size">Dê preferência para Two Way Async, com filas de request de longa duração.</p>



<h4 class="wp-block-heading">Chat Server</h4>



<p>Imagine que você tem um servidor de chat, são múltiplas instâncias do servidor. Cada cliente se conecta a uma instância do seu servidor via WebSocket. Isso produz o cenário onde as pessoas que participam de uma sala ou grupo podem estar conectadas em instâncias diferentes. Dado o volume de usuários e o limite de conexões WebSocket seu servidor escala dinamicamente criando mais instâncias do server.</p>



<p>O problema é que muitas vezes múltiplas instâncias precisam receber a mesma mensagem, é aqui que uma fila só não atende, <strong>pois uma fila não tem a capacidade de entregar a mesma mensagem para múltiplos consumidores</strong>.</p>



<p>Ao mesmo tempo, há a necessidade de assegurar que as mensagens só serão encaminhadas para instâncias que possuem usuários interessados, caso contrário será necessário publicar tudo para todas as instâncias, o que gera um flood desnecessário e ineficiente.</p>



<p>Nesse cenário, temos uma fila por instância do server. </p>



<p>Na inicialização de uma nova instância do server, sua fila de trabalho é criada e quando a instância morre, sua fila morre junto.</p>



<p>Não se preocupe com perda de mensagens, há tratamento adequado, porém chato, que usa desativação de binding, dead letters etc para evitar que a exclusão cause perda de mensagns.</p>



<h2 class="wp-block-heading">A regra de ouro</h2>



<p>Toda vez que uma mensagem precisa ser endereçada:</p>



<ul class="wp-block-list">
<li> A um consumidor específico, entre muitos. </li>



<li>Ou vários consumidores simultaneamente.</li>
</ul>



<p>Temos a necessidade de uma topologia diferente do padrão, e começamos a pensar em filas por consumidor.</p>



<h2 class="wp-block-heading">Entendendo Filas por Consumidor</h2>



<p>Fila por consumidor é uma topologia onde cada consumidor presente em um determinado fluxo possui sua própria fila de trabalho, não compartilhando essa fila com outros consumidores.</p>



<p>Ao lidar com esse tipo de fluxo, precisamos considerar algumas coisas:</p>



<ul class="wp-block-list">
<li>O consumidor (e sua fila) pode não estar mais disponível no momento em que enviamos uma mensagem para ele. O par de consumidor e fila podem ter morrido.</li>



<li>A morte do consumidor pode acontecer enquanto sua fila de trabalho ainda não foi inteiramente consumida.</li>
</ul>



<p>Assim, temos de nos preocupar tanto com o roteamento para filas que não existem mais, quanto como lidar com as mensagens não processadas quando uma fila é deletada.</p>



<p>Esses dois novos cenários se apresentam diante desse novo fluxo e temos mecanismos sofisticados presentes na plataforma para lidar com isso.</p>



<h3 class="wp-block-heading">Alternate Exchange</h3>



<p>As exchanges possuem um atribuido &#8220;alternate-exchange&#8221;. É uma forma elegante e robusta de lidar com mensagens não roteáveis no momento da publicação. </p>



<p>Em vez de deixar a mensagem ser descartada quando nenhum binding corresponde ao routing key, o broker encaminha essa mensagem automaticamente para uma exchange alternativa (&#8220;fallback&#8221;) definida como argumento da exchange original. Isso oferece uma trajetória determinística para captura, auditoria ou tratamento compensatório sem depender do flag mandatory do publisher ou de lógica de client-side.</p>



<p>Como funciona</p>



<ul class="wp-block-list">
<li>É um argumento da exchange: &#8220;alternate-exchange&#8221; =&gt; &#8220;&lt;nome-da-exchange&gt;&#8221; no momento da declaração de uma exchange.</li>



<li>Aciona-se quando uma mensagem chega a uma exchange e nenhum queue binding a essa exchange corresponde ao routing key.</li>



<li>O broker repassa a mensagem para a alternate exchange preservando headers, propriedades e routing key (ou seja, a mensagem chega intacta ao fallback).</li>



<li>A alternate exchange processa a mensagem como uma publicação normal: geralmente fanout, e pode ter suas próprias filas, DLX, TTL etc.</li>



<li>Se a alternate exchange também não conseguir roteá-la (e não tiver outra AE), a mensagem será descartada — portanto é importante projetar a AE para garantir captura (por exemplo, um fanout para uma fila de auditoria).</li>
</ul>



<p>Diferenças importantes vs outras estratégias</p>



<ul class="wp-block-list">
<li>vs mandatory + basic.return: mandatory instrui o broker a retornar a mensagem ao publisher quando não roteável; isso exige que o publisher trate o retorno e mantenha conexão síncrona. Alternate exchange remove essa obrigatoriedade do publisher e delega o fallback ao broker.</li>



<li>vs dead-letter exchange (DLX): DLX trata mensagens que já entraram em uma fila e foram rejeitadas, expiraram ou atingiram max-length. Alternate exchange atua no momento da publicação, antes mesmo de qualquer fila existir.</li>



<li>vs publisher confirms: confirms garantem que o broker recebeu a mensagem; não dizem nada sobre roteabilidade — AE lida especificamente com mensagens não roteáveis.</li>
</ul>



<p>Com Alternate Exchange conseguimos fazer com que, caso um consumidor não exista, a mensagem possa ser encaminhada para uma fila de controle e possa ser reprocessada por outro mecanismo, ou ainda possa seguir outro fluxo. </p>



<h3 class="wp-block-heading">Dead Letter</h3>



<p>Dead-lettering é o mecanismo padrão para capturar mensagens que não puderam ser processadas normalmente dentro de uma fila — seja por rejeição explícita, expiração ou limites de tamanho ou até <strong>PELA EXCLUSÃO DA FILA</strong>. O padrão permite redirecionar essas mensagens para uma exchange de tratamento (dead-letter exchange, DLX) e, tipicamente, para uma dead-letter queue (DLQ) onde serão inspeccionadas, reprocessadas ou descartadas de forma controlada. </p>



<p>Como funciona</p>



<ul class="wp-block-list">
<li>Configuração: você declara uma fila com argumentos: x-dead-letter-exchange = &#8220;&lt;nome-dlx&gt;&#8221; e opcionalmente x-dead-letter-routing-key = &#8220;&lt;rk&gt;&#8221;.</li>



<li>Gatilhos de dead-letter:
<ul class="wp-block-list">
<li>consumer chama basic.reject/basic.nack com requeue=false;</li>



<li>mensagem expira por TTL (x-message-ttl em fila ou header expiration);</li>



<li>fila atinge max-length/max-length-bytes (mensagens que excederem são dead-lettered).</li>



<li>fila é deletada</li>
</ul>
</li>



<li>Quando dead-lettered, o broker publica a mensagem na DLX como uma nova publicação. RabbitMQ acrescenta/atualiza o header x-death (contendo contagem, motivo, queue original, exchange original, timestamp) para rastrear histórico de rejeições.</li>
</ul>



<h2 class="wp-block-heading">Conectando os pontos</h2>



<p>Essas abordagens fazem uso não somente das operações QueueDeclareAsync e QueueBindAsync, mas também fazem uso de parâmetros pouco discutidos das filas. Esses casos de uso nos fazem começar a pensar nas flags:</p>



<ul class="wp-block-list">
<li>durable: <strong>Essa fila deve sobreviver à reinicialização do broker?</strong></li>



<li>exclusive: <strong>O uso desta fila deve ser limitado à conexão declarada? Essa fila será excluída quando a conexão declarada for encerrada.</strong></li>



<li>autoDelete: <strong>Essa fila deve ser excluída automaticamente quando seu último consumidor (se houver) cancelar a assinatura?</strong></li>
</ul>



<p>Esses parâmetros começam a ganhar vida e fazer sentido com esses tipos de caso de uso e abordagem.</p>



<p>De um lado, eliminando esforço manual de manutenção (exclusão) de filas quando elas não são mais úteis. Por outro asseguram que o consumo físico por múltiplos consumidores de diferentes conexões não será permitido, eliminando a necessidade de controle adicional.</p>



<h2 class="wp-block-heading">Quem deve criar filas, exchanges e binds?</h2>



<p>É super comum encontrar times discutindo de quem é a responsabilidade de criar filas, exchanges e binds, ou seja, a topologia de objetos do RabbitMQ. </p>



<p>É por conta dessa segunda camada de complexidade, quando nos deparamos com casos de uso mais complexos, que não há uma decisão que seja universalmente correta.</p>



<p>A depender das estratégias, temos demandas diferentes que precisam de abordagens diferentes. </p>



<p>A topologia no RabbitMQ é decisão de arquitetura, a depender da estratégia, essa criação pode, sim, ser uma tarefa de Infra ou Ops, ou DevOps, mas há casos de uso em que claramente é o código da aplicação quem tem de fazê-lo. E, em alguns casos, por operação, como descrito no case de RPC. </p>



<p>Assim, é importante entender que o correto a ser adotado é aquilo que traga a menor carga cognitiva, menor demanda por operação e manutenção e esforço diário.</p>



<p>Sem entender quais padrões serão adotados, não é possível determinar de quem é a responsabilidade, portanto, qualquer tentativa de atribuir um responsável, sem que saibamos quais padrões usaremos, seria inútil.</p>



<p>A distância entre o overview e o mundo real é muito grande, e por isso é importante aprofundar no entendimento dos padrões ao redor de Mensageria em especial do RabbitMQ, pois geralmente muitas das respostas simplesmente aparecem ao entender o comportamento.</p>



<p>Acredito que fazer provas de conceito seja uma forma muito eficiente de estudar, mesmo que você não tenha demanda para esse tipo de topologia.</p>



<p>Nos últimos 3 anos me deparei pela primeira vez com demandas que endereçaram um tipo específico de topologia que havia estudado há quase 10 anos e que ficaram engavetados esse tempo todo.</p>



<p>Somente conhecendo, mesmo que não implementando de fato, foi possível adotá-los anos mais tarde, mas porque o conceito estava devidamente absorvido, e muitas vezes isso demanda tempo.</p>



<figure class="wp-block-image size-large"><img data-dominant-color="252829" data-has-transparency="false" style="--dominant-color: #252829;" loading="lazy" decoding="async" width="1024" height="546" src="https://gago.io/wp-content/uploads/2025/08/image-1024x546.png" alt="" class="wp-image-20783 not-transparent" srcset="https://gago.io/wp-content/uploads/2025/08/image-1024x546.png 1024w, https://gago.io/wp-content/uploads/2025/08/image-980x522.png 980w, https://gago.io/wp-content/uploads/2025/08/image-480x256.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Acima mostro algumas configurações necessárias para lidar com ordenação determinística em filas efêmeras. Essa quantidade de flags é uma demonstração de que quando saímos do overview, o básico feijão com arroz já não atende mais. E para conseguir entregar esses cenários avançados, é preciso ir mais fundo.</p>



<p></p>



<p></p>



<p></p>The post <a href="https://gago.io/blog/rabbitmq-filas-efemeras/">RabbitMQ: Filas efêmeras</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/rabbitmq-filas-efemeras/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Respondendo: Até que ponto isolamento de Bases de Dados em Microsserviços faz sentido?</title>
		<link>https://gago.io/blog/microsservico-ate-que-ponto-isolar-dados/</link>
					<comments>https://gago.io/blog/microsservico-ate-que-ponto-isolar-dados/#comments</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Fri, 04 Jul 2025 11:39:27 +0000</pubDate>
				<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Cloud Native .NET]]></category>
		<category><![CDATA[RabbitMQ para Aplicações .NET]]></category>
		<category><![CDATA[MicroServices]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20738</guid>

					<description><![CDATA[<p>Recentemente, um aluno questionou no nosso fórum interno sobre abordagens como Database-server-per-service, Database-per-service, Schema-per-service e Private-tables-per-service no contexto de microsserviços. Esse é um tema recorrente quando o assunto é microsserviços e não há resposta satisfatória. A resposta para essa pergunta sempre será uma pedra no calcanhar. Ao primeiro olhar, parece exagero, um tipo de purismo [&#8230;]</p>
The post <a href="https://gago.io/blog/microsservico-ate-que-ponto-isolar-dados/">Respondendo: Até que ponto isolamento de Bases de Dados em Microsserviços faz sentido?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Recentemente, um aluno questionou no nosso fórum interno sobre abordagens como <strong><span style="color: #03a56a;" class="stk-highlight">Database-server-per-service</span></strong>, <strong><span style="color: #14c957;" class="stk-highlight">Database-per-service</span></strong>, <strong><span style="color: #dd6b6b;" class="stk-highlight">Schema-per-service</span></strong> e <strong><span style="color: #ff0000;" class="stk-highlight">Private-tables-per-service</span></strong> no contexto de microsserviços.</p>



<p>Esse é um tema recorrente quando o assunto é microsserviços e não há resposta satisfatória. <br />A resposta para essa pergunta sempre será uma pedra no calcanhar.</p>



<p>Ao primeiro olhar, parece exagero, um tipo de purismo desnecessário. Principalmente por inviabilizar algumas facilidades presentes em monolitos que compartilham seus objetos (tabelas e views) de banco e fazem integração entre serviços via esses objetos.</p>



<p>Sim, esse é um tema espinhoso, difícil de engolir e por isso vamos abordá-lo hoje.</p>



<span id="more-20738"></span>



<p>No nosso discord privado do <strong>Cloud Native .NET</strong> e <strong>Mensageria .NET</strong> temos um fórum para os alunos e eventualmente surgem algumas perguntas memoráveis.</p>



<p><strong>Essa foi uma delas do Rodrigo:</strong></p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-stackable-columns stk-block-columns stk-block stk-a4884d1 stk-block-background stk--has-background-overlay" data-block-id="a4884d1"><style>.stk-a4884d1 {background-color:linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%) !important;}.stk-a4884d1:before{mix-blend-mode:screen !important;background-image:linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%) !important;}</style><div class="stk-row stk-inner-blocks stk-block-content stk-content-align stk-a4884d1-column">
<div class="wp-block-stackable-column stk-block-column stk-column stk-block stk-6b6ffe3" data-v="4" data-block-id="6b6ffe3"><div class="stk-column-wrapper stk-block-column__content stk-container stk-6b6ffe3-container stk--no-background stk--no-padding"><div class="has-text-align-left stk-block-content stk-inner-blocks stk-6b6ffe3-inner-blocks">
<p><em>No que se refere a comunicação entre microsserviços, o banco de dados de um microsserviço não pode ser acessado diretamente por outro microsserviço, e um dos principais pontos é manter a base desses microsservicos privada sem que possa ser acessada diretamente </em></p>



<p><em>Para manter esse isolamento vejo muitas referências ao &#8220;Database-server-per-service&#8221; ou seja, cada serviço têm seu próprio bando de dados, na verdade essa é a maioria das referências que encontro </em></p>



<p><em>Mas pelo que pude verificar para manter o isolamento dos dados de cada microsserviço existem outras alternativas:</em></p>



<ul class="wp-block-list">
<li><em>Schema-per-service: Não necessário um banco de dados para cada microsserviço, em uma única base de dados cada microsserviço tem seu próprio schema com suas tabelas associadas, permitindo dessa forma o isolamento de dados entre microsserviços</em></li>



<li><em>Private-tables-per-service: Não necessário um banco de dados para cada serviço, em uma única base de dados cada serviço tem suas tabelas privadas sendo apenas acessadas pelo serviço específico, permitindo dessa forma o isolamento entre microsserviços,</em></li>
</ul>



<p>Pergunto:</p>



<ul class="wp-block-list">
<li><em>A) Qual a sua visão sobre Schema-per-service e Private-tables-per-service? Utilizaria algum dos dois? </em></li>



<li><em>B) Levando apenas essa questão de isolamento de dados entre microsservicos, ao utilizar Schema-per-service ou Private-tables-per-service estou de fato trabalhando com microsserviços? </em></li>



<li><em>C) Se a resposta para o item B for não, apenas Database-server-per-service garante de fato que estou trabalhando com microsserviços?</em></li>
</ul>
</div></div></div>
</div></div>
</div>
</div>



<p class="has-text-align-right">Rodrigo Alves de Oliveira</p>



<p>Então, vamos à resposta:</p>



<p>A gente não pode olhar para essa demanda de isolamento sem entender seu motivo. O argumento central ou os argumentos centrais que produzem essa demanda. </p>



<p>Sem isso, podemos sabotar a estratégia.</p>



<p>O isolamento dos objetos de banco de um microsserviço se dá para que, somente o próprio microsserviço produza dependência com esses objetos. Ou seja, somente o próprio microsserviço sabe quais são as tabelas, colunas e relacionamentos. </p>



<p>O motivo para isso, é assegurar que o microsserviço poderá MUDAR essas estruturas para atender novas demandas. Sem gerar impacto a ninguém. Basta manter o contrato nas API&#8217;s lá na camada mais alta, que todo mundo que depende dele, não saberá e não precisará saber se e caso uma coluna nova for adicionada, ou uma coluna virar uma tabela inteira nova.</p>



<p>Quando movemos o nível da dependência para a API, temos a chance de fazer mudanças internas. Como fragmentar o banco em um banco relacional com dados complementares e históricos em bancos não relacionais.</p>



<p class="has-medium-font-size">Tudo se torna possível, e o mais importante é, antes de mais nada, evitar os puxadinhos que miram em &#8220;<strong>não fazer o que precisava ser feito para não impactar outros serviços que dependem das minhas tabelas.</strong>&#8220;</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="has-small-font-size">Sabe a tal independência do Microsserviço?<br />A capacidade de ter um roadmap próprio, sem afetar outros microsserviços?<br />A capacidade de reestruturar para atender novas demandas sem impactar suas dependências?<br />É sobre isso! É sobre o alicerce, sobre o pilar central da necessidade de microsserviços: <br />Entrega de negócio rápida independente dos demais serviços.<br /><strong>Se você entender isso, aceitar isso, pode rasgar uma pá de livros e deletar dos favoritos mais de 90% dos conteúdos em texto e vídeo sobre microsserviços.</strong></p>
</blockquote>



<p>Agora, com esse isolamento, você tem a chance de fazer o certo.</p>



<p>Aí você pode dizer:<strong> mas fulano depende desse dado, nessa estrutura</strong>. Ok, crie uma API compatível com o formato antigo. Dificilmente o dado deixou de existir, ele geralmente muda de estrutura interna de armazenamento. Mas não se perde.</p>



<p>Esse isolamento permite que quem estiver evoluindo o microsserviço possa pensar em fazer o correto e necessário em vez de fazer o que dá para ser feito, dada as dependências de baixo nível.</p>



<p>Em última análise, tem relação com evitar fazer gambiarra. Evitar mudanças difíceis, só porque alguém depende de algo que é interno ao meu microsserviço.</p>



<p>Dito isso, não importa como você faça:</p>



<ul class="wp-block-list">
<li>Desde que sejam connectionstrings diferentes, uma para cada microsserviço.</li>



<li>Desde que seja fisicamente impossível (no ambiente de desenvolvimento local) criar um simples join entre tabelas de 2 microsserviços, </li>



<li>Desde que seja impossível fazer uma única transação que englobe operações em 2 microsserviços.</li>
</ul>



<p>Se você consegue usar um só database, com um só schema, mas consegue via usuários e permissões de banco, não deixar o dev fazer sequer um JOIN que ultrapasse os limites do próprio microsserviço, já atende! </p>



<p>Eu particularmente não gosto dessa abordagem e desaconselho uma abordagem tão frágil.</p>



<p>Considero essa abordagem algo como <strong>amarrar cachorro com linguiça</strong>. Na máquina do dev, com poderes ilimitados, se o dev for minimamente descolado, se ele pode, ele vai criar um terceiro usuário na máquina dele, só para poder fazer esse join, só para ficar mais fácil. </p>



<p>Porque, na cabeça dele, é &#8220;incoerente&#8221; não poder fazer esse join se as tabelas que ele precisa estiverem lado-a-lado, no mesmo schema, ou no mesmo database. </p>



<p>Então, minha estratégia se pauta em ISOLAR no mínimo por database (na mesma instância de servidor) em DEV. Isso me assegura que esses joins não sejam sequer possíveis tecnicamente (no postgres) <a href="https://wiki.postgresql.org/wiki/FAQ#How_do_I_perform_queries_using_multiple_databases.3F" target="_blank" rel="noreferrer noopener">https://wiki.postgresql.org/wiki/FAQ#How_do_I_perform_queries_using_multiple_databases.3F</a><a href="https://wiki.postgresql.org/wiki/FAQ" target="_blank" rel="noreferrer noopener">FAQ</a>. </p>



<p class="has-medium-font-size">Assim, se eu consigo assegurar que em dev, ele respeitou esse isolamento, eu tenho todos os patterns disponíveis em produção e usar qualquer um deles vira uma decisão de deploy, de infra.</p>



<p>Se ele respeitar essa premissa de isolamento, </p>



<p><img decoding="async" src="https://discord.com/assets/070ce105e9621145.svg" alt="👉"/> Podemos usar 1 só database com 1 só schema, (desde que não haja conflito). <br /><img decoding="async" src="https://discord.com/assets/070ce105e9621145.svg" alt="👉"/> Assim como podemos usar um schema por microsserviço. <br /><img decoding="async" src="https://discord.com/assets/070ce105e9621145.svg" alt="👉"/> Ou podemos usar databases por microsserviço. <br /><img decoding="async" src="https://discord.com/assets/070ce105e9621145.svg" alt="👉"/> e, em última análise, até instâncias de servidor por microsserviço. <br /><strong>E essa decisão vira uma decisão de infra.</strong></p>



<p>Agora vamos falar de gosto. Na escala temos:</p>



<ul class="wp-block-list">
<li>Mesmo Server / Mesmo Database/ Mesmo Schema</li>



<li>Mesmo Server / Mesmo Database/ Schema diferente</li>



<li><img decoding="async" src="https://discord.com/assets/070ce105e9621145.svg" alt="👉"/> Mesmo Server / Database Diferente</li>



<li>Server Diferente</li>
</ul>



<p>Olhando para o postgres, sql server e mysql tendo a optar por Database-per-service.</p>



<p>Um único servidor postgres com vários databases, onde cada database tem um único dono, pertence a um único microsserviço. E malandramente, com usuários de banco diferentes com senhas diferentes para produzir connectionstrings diferentes, de forma que não seja possível ferir o isolamento.</p>



<p>Sigo a regra de que: <span style="color: #ff0000;" class="stk-highlight"><strong>O certo tem de ser fácil, e o errado tem de ser fisicamente impossível</strong></span>.</p>



<p>B) A pergunta que você precisa se fazer para saber se tem um microsserviço mesmo é: </p>



<p>Se mantivermos os contratos das API&#8217;s (endpoints e estruturas de dados, eventos e comandos) ainda assim consigo evoluir meu microsserviço (criando novas tabelas e mudando as anteriores) sem quebrar ninguém?</p>



<p>Porque no final do dia é isso que importa. </p>



<p>Assegurar que se uma gambiarra for feita, se um puxadinho for feito visando não atrapalhar as dependências é por um erro individual e pessoal de uma pessoa e não por uma situação mais ampla em que a arquitetura empurrou o dev. No final do dia é sobre fazer o que precisa ser feito, em vez de fazer o que dá porque há dependências.</p>



<p>Todas as estratégias que isolem essas mudanças pelo aspecto funcional são válidas.</p>



<p>Mas tem questões práticas que precisamos nos atentar.</p>



<p>Se você usa migrations, lembre-se que Migrations possui a necessidade de uma tabela de controle de versão. Portanto,<em> Private-tables-per-service</em> vai exigir configuração adicional para criar múltiplas tabelas de controle no mesmo schema de banco, uma para cada microsserviço. </p>



<p><em>Private-tables-per-service</em> inevitavelmente fisicamente permite que faça um join que atravesse múltiplos microsserviços. Aqui temos um problema adicional. Se você implementar uma política de dogfooding onde os devs partilham do dia-a-dia de produção provando da própria comida de cachorro, se ele usa esse tipo de JOIN para validações, ele vai atrasar o máximo possível para realizar uma eventual fragmentação em múltiplos data stores diferentes.</p>



<p>Só quando for impossível lidar com essas estruturas em um só banco, ele vai cogitar essa mudança. Enquanto isso, será o cara que defenderá com unhas e dentes que a abordagem é desnecessária. Principalmente por tirar dele a simplicidade do troubleshooting simplificado.</p>



<p>Ou seja, se você deixar que alguém possa se acomodar com um viés monolítico, esse comodismo pode custar caro.</p>



<p>Então eu nunca deixaria uma oportunidade de fazer esse JOIN fisicamente, porque quando você está distraído, quando as coisas estão funcionando de boa. Quando você está confortável e feliz com o resultado, é quando você baixa a guarda e reduz a tensão, e é quando tudo desmorona porque alguém tomou uma decisão estúpida. Eu tomaria necessariamente a decisão mais barata que inviabilizasse esse acoplamento.</p>



<p>Então, eu tendo a usar database dentro do mesmo servidor. Essa é a opção que me permite mover com facilidade em caso de crescimento. E gera independência para inclusive ter parâmetros diferentes de database para database. O que me permite ter um controle bem interessante. Mesmo que use um mesmo database server.</p>



<p>Respondido?</p>



<p>(fiz algumas adaptações na escrita)</p>



<ul class="wp-block-list"></ul>The post <a href="https://gago.io/blog/microsservico-ate-que-ponto-isolar-dados/">Respondendo: Até que ponto isolamento de Bases de Dados em Microsserviços faz sentido?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/microsservico-ate-que-ponto-isolar-dados/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Simulando I/O limitado com Docker: como testar aplicações sob restrições realistas</title>
		<link>https://gago.io/blog/docker-simulando-io/</link>
					<comments>https://gago.io/blog/docker-simulando-io/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Thu, 12 Jun 2025 18:31:32 +0000</pubDate>
				<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Docker de A a Z]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[RabbitMQ]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20707</guid>

					<description><![CDATA[<p>Testar aplicações é um desafio recorrente e complexo na engenharia de software. Porém, quando o cenário envolve restrições específicas de I/O — como latência de disco, throughput de leitura/escrita reduzido ou limitação de operações por segundo — o desafio ganha outra dimensão. Nesse contexto, a capacidade de simular gargalos de I/O torna-se uma ferramenta poderosa [&#8230;]</p>
The post <a href="https://gago.io/blog/docker-simulando-io/">Simulando I/O limitado com Docker: como testar aplicações sob restrições realistas</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Testar aplicações é um desafio recorrente e complexo na engenharia de software. Porém, quando o cenário envolve restrições específicas de I/O — como latência de disco, throughput de leitura/escrita reduzido ou limitação de operações por segundo — o desafio ganha outra dimensão.</p>



<p>Nesse contexto, a capacidade de simular gargalos de I/O torna-se uma ferramenta poderosa para validar a resiliência, eficiência e tolerância a falhas da sua aplicação. Felizmente, o Docker oferece mecanismos para isso, permitindo configurar limites finos de I/O em containers.</p>



<p>Não é todo dia que você é exposto a esse tipo de necessidade, como fui recentemente. Entretanto, a capacidade de ter à mão, algo tão simples quanto poderoso, permite validar e experimentar problemas previsíveis, muito antes de sequer contratar uma infra em produção.</p>



<p>É mais do que validar um setup, é sobre entregar previsibilidade arquietural.</p>



<p>Vamos explorar como isso pode ser feito, por que é útil e quais problemas essa abordagem ajuda a evitar.</p>



<span id="more-20707"></span>



<h2 class="wp-block-heading">Por que simular limitações de I/O?</h2>



<p>Sistemas modernos frequentemente dependem de bancos de dados, message brokers, object storage e sistemas de arquivos em rede. Em ambientes de produção, esses recursos estão sujeitos a:</p>



<ul class="wp-block-list">
<li>Sobrecarga de IOPS (Input/Output Operations per Second);</li>



<li>Baixa largura de banda de leitura/escrita;</li>



<li>Latência provocada por armazenamento remoto ou compartilhado;</li>



<li>Comportamentos erráticos em períodos de alta concorrência.</li>
</ul>



<p class="has-white-color has-vivid-cyan-blue-to-vivid-purple-gradient-background has-text-color has-background has-link-color has-medium-font-size wp-elements-8f84fe17a88e9b768fb7201ac9488e1c">Existe um fenômeno que não é isolado nem tão raro, em que certos tipos de ambiente/empresa onde os notebooks e/ou desktops dos desenvolvedores podem ter vezes mais capacidade de I/O, CPU e memória do que um servidor de produção.</p>



<p></p>



<p>Me recordo de uma compra de 2023 onde fiz compra de SSD para o meu desktop de desenvolvimento, focando quase que exclusivamente em throughput.</p>



<figure class="wp-block-image aligncenter size-full"><img data-dominant-color="f2f2f3" data-has-transparency="false" style="--dominant-color: #f2f2f3;" loading="lazy" decoding="async" width="1659" height="159" src="https://gago.io/wp-content/uploads/2025/06/image-1.png" alt="" class="wp-image-20710 not-transparent" srcset="https://gago.io/wp-content/uploads/2025/06/image-1.png 1659w, https://gago.io/wp-content/uploads/2025/06/image-1-1280x123.png 1280w, https://gago.io/wp-content/uploads/2025/06/image-1-980x94.png 980w, https://gago.io/wp-content/uploads/2025/06/image-1-480x46.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) and (max-width: 1280px) 1280px, (min-width: 1281px) 1659px, 100vw" /></figure>



<figure class="wp-block-image aligncenter size-large is-resized"><img data-dominant-color="edecea" data-has-transparency="false" loading="lazy" decoding="async" width="1024" height="370" src="https://gago.io/wp-content/uploads/2025/06/image-2-1024x370.png" alt="" class="wp-image-20711 not-transparent" style="--dominant-color: #edecea; width:540px;height:auto" srcset="https://gago.io/wp-content/uploads/2025/06/image-2-1024x370.png 1024w, https://gago.io/wp-content/uploads/2025/06/image-2-980x354.png 980w, https://gago.io/wp-content/uploads/2025/06/image-2-480x173.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<figure class="wp-block-image alignleft size-full is-resized"><img data-dominant-color="6c6b72" data-has-transparency="true" loading="lazy" decoding="async" width="1024" height="1024" src="https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_.png" alt="" class="wp-image-20712 has-transparency" style="--dominant-color: #6c6b72; object-fit:cover;width:436px;height:auto" srcset="https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_.png 1024w, https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_-980x980.png 980w, https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_-480x480.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Por mais que o desktop possa estar sub um sistema operacional &#8220;pesado&#8221;, com várias instâncias de visual studio abertas, e vários npm installs dragando I/O preenchendo node_modules com toda a internet, ainda consigo ter uma máquina responsiva para fazer isso tudo enquanto gravo uma aula, um vídeo para o YouTube ou algo assim. </p>



<p>Esse é o meu caso de uso.</p>



<p>Outros compram hardware eficiente por puro prazer, satisfação, ou mesmo para jogar, já que alguns usam o mesmo equipamento para essa finalidade.</p>



<p>Há muitos motivos para existir essa discrepância entre o desktop/notebook do desenvolvedor e o ambiente de produção e, portanto, precisamos tomar algum cuidado se não estivermos atentos ao quanto de hardware estamos entregando para os nossos servidores produtivos.</p>



<p>Por isso, simular restrições é importante.</p>



<p>Ao simular essas restrições longe do ambiente produtivo conseguimos:</p>



<ul class="wp-block-list">
<li>Avaliar <strong>comportamento da aplicação sob stress realista</strong>;</li>



<li>Validar estratégias de <strong>retry, timeout e fallback</strong>;</li>



<li>Identificar <strong>gargalos arquiteturais ocultos</strong>;</li>



<li>Prevenir falhas críticas <strong>antes que elas atinjam produção</strong>.</li>
</ul>



<p>Mas como docker pode nos ajudar com isso?</p>



<h2 class="wp-block-heading">Docker e controle de I/O: os parâmetros que importam</h2>



<p>O Docker permite impor limites de I/O por container usando parâmetros específicos no momento de execução. São eles:</p>



<h3 class="wp-block-heading"><code>--device-read-bps="&lt;dispositivo&gt;:&lt;limite&gt;"</code></h3>



<p>Limita a taxa de leitura (em bytes por segundo) de um determinado dispositivo.</p>



<p>Exemplo:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/sh

docker run --device-read-bps /dev/sda:10mb ubuntu
</pre>



<p>Este comando limita o container a ler no máximo 10 MB/s do dispositivo <code>/dev/sda</code>.</p>



<h3 class="wp-block-heading"><code>--device-write-bps="&lt;dispositivo&gt;:&lt;limite&gt;"</code></h3>



<p>Controla o throughput de escrita no dispositivo.</p>



<p>Exemplo:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/sh

docker run --device-write-bps /dev/sda:5mb ubuntu
</pre>



<p>Limita a escrita a 5 MB/s, útil para simular discos lentos ou saturados.</p>



<h3 class="wp-block-heading"><code>--device-read-iops="&lt;dispositivo&gt;:&lt;limite&gt;"</code></h3>



<p>Impõe limite ao número de operações de leitura por segundo (IOPS).</p>



<p>Exemplo:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/sh

docker run --device-read-iops /dev/sda:100 ubuntu
</pre>



<p>Aqui, o container poderá fazer no máximo 100 operações de leitura por segundo.</p>



<h3 class="wp-block-heading"><code>--device-write-iops="&lt;dispositivo&gt;:&lt;limite&gt;"</code></h3>



<p>Faz o mesmo para escrita, restringindo a IOPS de gravação.</p>



<p>Exemplo:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">#!/bin/sh

docker run --device-write-iops /dev/sda:50 ubuntu
</pre>



<h2 class="wp-block-heading">Casos de uso reais em ambientes .NET</h2>



<h3 class="wp-block-heading">1. <strong>Testes com EF Core sob disco lento</strong></h3>



<p>Simule um ambiente de banco de dados em disco mecânico para validar o comportamento de consultas N+1, migrações e cenários onde o <code>DbContext.SaveChanges()</code> leva segundos para concluir.</p>



<h3 class="wp-block-heading">2. <strong>Simulação de message brokers com I/O degradado</strong></h3>



<p>Ao limitar o throughput do volume onde o RabbitMQ ou Kafka armazena os logs de mensagens, é possível validar o comportamento do seu sistema sob latência, inclusive testando dead-letter queues e redelivery policies.</p>



<h3 class="wp-block-heading">2. <strong>Simulação de serviços de armazenamento baseados em Lucene</strong></h3>



<p>ElasticSearch ou Solr podem ser gravemente afetados pela falta de I/O do disco, podendo causar até, em última análise, perdas financeiras, quando mal provisionados. Já presenciei uma gravação consumir 1 segundo inteiro em uma chamada de API.</p>



<h3 class="wp-block-heading">3. <strong>Serviços que realizam processamento em lote</strong></h3>



<p>Serviços de ETL ou processamento batch que dependem de grandes volumes de leitura e escrita podem ser testados quanto à degradação de performance — inclusive validando alertas de observabilidade com Prometheus e Grafana.</p>



<h2 class="wp-block-heading">Benefícios: o que essa abordagem evita</h2>



<ul class="wp-block-list">
<li><strong>Falhas em produção causadas por ambientes otimizados de desenvolvimento</strong> (SSD local vs. disco remoto);</li>



<li><strong>Code paths não exercitados</strong>, como retry policies, circuit breakers e timeouts mal configurados;</li>



<li><strong>Impressão falsa de performance</strong> por testes realizados apenas em condições ideais;</li>



<li><strong>Problemas de throughput</strong> que só aparecem com carga real e concorrência.</li>
</ul>



<h2 class="wp-block-heading">O que é possível validar antes de chegar à produção</h2>



<p>Ambientes controlados permitem medir:</p>



<ul class="wp-block-list">
<li><strong>Tempo médio de resposta em cenários com I/O degradado</strong>;</li>



<li><strong>Eficiência do uso de cache (Redis, MemoryCache, etc)</strong>;</li>



<li><strong>Resiliência do sistema a falhas intermitentes de armazenamento</strong>;</li>



<li><strong>Capacidade de fallback ou uso de circuit breakers (ex: Polly no .NET)</strong>;</li>



<li><strong>Alertas reais de latência e disponibilidade (observabilidade realista)</strong>.</li>
</ul>



<h2 class="wp-block-heading">Até que ponto vale a pena?</h2>



<p>A tabela abaixo foi gerada pelo chatGPT (<a href="https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html?" target="_blank" rel="noopener" title="mas temos a doc aqui">mas temos a doc aqui</a>).</p>



<figure class="wp-block-image size-large"><img data-dominant-color="272727" data-has-transparency="false" style="--dominant-color: #272727;" loading="lazy" decoding="async" width="1024" height="533" src="https://gago.io/wp-content/uploads/2025/06/image-3-1024x533.png" alt="" class="wp-image-20724 not-transparent" srcset="https://gago.io/wp-content/uploads/2025/06/image-3-1024x533.png 1024w, https://gago.io/wp-content/uploads/2025/06/image-3-980x510.png 980w, https://gago.io/wp-content/uploads/2025/06/image-3-480x250.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Esses números podem servir de base para testes alinhados a ambientes produtivos. Caso eles não digam muita coisa, entenda o custo de cada um, estamos falando de uma variação muito significativa.</p>



<p>Se compararmos a um bom SSD, não tão incomum em um desktop de desenvolvimento, vemos o valor absurdo pago a depender do cloud provider.</p>



<figure class="wp-block-image size-full"><img data-dominant-color="f5f4f4" data-has-transparency="false" style="--dominant-color: #f5f4f4;" loading="lazy" decoding="async" width="1025" height="277" src="https://gago.io/wp-content/uploads/2025/06/image-4.png" alt="" class="wp-image-20726 not-transparent" srcset="https://gago.io/wp-content/uploads/2025/06/image-4.png 1025w, https://gago.io/wp-content/uploads/2025/06/image-4-980x265.png 980w, https://gago.io/wp-content/uploads/2025/06/image-4-480x130.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1025px, 100vw" /><figcaption class="wp-element-caption"><a href="https://www.kingston.com/br/ssd/gaming/kingston-fury-renegade-nvme-m2-ssd?capacity=4tb&amp;options=heat%20spreader" target="_blank" rel="noopener" title="Link">Link</a></figcaption></figure>



<p>Entregar na cloud, números comparáveis com esses custa muito caro, portanto, testar seus recursos e aplicações com base nesses números permite entender o que funciona, o que não funciona e qual o risco.</p>



<h2 class="wp-block-heading">Conclusão: simular é prevenir</h2>



<p>Testar sob limitações de I/O é uma prática essencial para aplicações críticas, especialmente em sistemas distribuídos. Com Docker, é possível incorporar essa etapa à sua esteira de qualidade sem depender de hardware específico.</p>



<p>Essa abordagem vai além do “testa que funciona”: ela antecipa o imprevisível e prepara o sistema para o pior cenário possível.</p>



<p>Use os parâmetros de limitação de I/O do Docker como parte do seu arsenal de testes — porque em produção, o inesperado não perdoa.</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img data-dominant-color="694041" data-has-transparency="true" loading="lazy" decoding="async" width="1024" height="1024" src="https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_-1.png" alt="" class="wp-image-20727 has-transparency" style="--dominant-color: #694041; width:309px;height:auto" srcset="https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_-1.png 1024w, https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_-1-980x980.png 980w, https://gago.io/wp-content/uploads/2025/06/dev-desktop-servidorproducao.fw_-1-480x480.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<h3 class="wp-block-heading">PS: Não dá para simular algo que seu hardware sequer entrega.</h3>



<p class="has-small-font-size"><strong><span style="color: #ff0000;" class="stk-highlight">O óbvio precisa ser dito.</span></strong></p>



<p>Se seu desktop não tem hardware suficiente para esse teste, se não tem um hardware mais eficiente que o servidor na cloud. Então não há muito a ser feito.</p>



<p>Ainda não validei, mas um teste futuro que quero fazer é sob o tmpfs, criando um volume que em vez de usar disco, utiliza memória RAM.</p>



<p>Dessa forma seria possível entregar um file system mais rápido do que qualquer um dos seus discos ou ssd&#8217;s.</p>The post <a href="https://gago.io/blog/docker-simulando-io/">Simulando I/O limitado com Docker: como testar aplicações sob restrições realistas</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/docker-simulando-io/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>O perigo silencioso das conexões stateful: por que respeitar o ciclo de vida importa?</title>
		<link>https://gago.io/blog/perigo-conexoes-stateful/</link>
					<comments>https://gago.io/blog/perigo-conexoes-stateful/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Tue, 03 Jun 2025 19:11:52 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Desenvolvimento]]></category>
		<category><![CDATA[Software Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20695</guid>

					<description><![CDATA[<p>APIs HTTP dominam por décadas o paradigma de comunicação entre aplicações, é fácil cair na armadilha de tratar qualquer conexão como se fosse apenas mais uma requisição stateless, principalmente conexões stateful &#8220;sob HTTP&#8221;. Mas nem toda conexão é igual. E conexões de longa duração merecem muito mais respeito. O erro de subestimar o gRPC Quantos [&#8230;]</p>
The post <a href="https://gago.io/blog/perigo-conexoes-stateful/">O perigo silencioso das conexões stateful: por que respeitar o ciclo de vida importa?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>APIs HTTP dominam por décadas o paradigma de comunicação entre aplicações, é fácil cair na armadilha de tratar qualquer conexão como se fosse apenas mais uma requisição stateless, principalmente conexões stateful &#8220;sob HTTP&#8221;. Mas nem toda conexão é igual. </p>



<p>E conexões de longa duração merecem muito mais respeito.</p>



<span id="more-20695"></span>



<h2 class="wp-block-heading">O erro de subestimar o gRPC</h2>



<p>Quantos projetos gRPC já fracassaram por partir da premissa equivocada de que, por usar HTTP/2, gRPC é apenas uma evolução natural do REST? A diferença fundamental é que o HTTP/2 habilita conexões multiplexadas e persistentes, o que muda drasticamente a forma como devemos gerenciar recursos, lidar com timeouts e projetar a resiliência do sistema.</p>



<p>Tratar gRPC como um simples request/response stateless é ignorar toda a complexidade (e o poder) de um canal persistente. Isso leva a falhas silenciosas, gargalos e uma gestão de recursos ineficiente.</p>



<h2 class="wp-block-heading">Websockets e a armadilha do heartbeat</h2>



<p>A história se repete com Websockets. Quantas aplicações mantêm milhares de conexões quase inativas, sustentadas apenas por heartbeats? Cada conexão viva consome memória, sockets e ciclos de CPU, mesmo que esteja ociosa. O resultado? Uma <strong><span style="color: #ff0000;" class="stk-highlight">necessidade artificial de escalar horizontalmente</span></strong>, adicionando dúzias de máquinas apenas para manter um pool de conexões abertas.</p>



<p>O custo oculto das conexões long-lived mal gerenciadas é real e <span style="color: #ff0000;" class="stk-highlight"><strong>afeta diretamente a escalabilidade e a sustentabilidade financeira da solução.</strong></span></p>



<h2 class="wp-block-heading">Alternância dinâmica entre WebSocket e polling</h2>



<p>Uma abordagem possível e adotada por sistemas modernos é a capacidade de alternar dinamicamente entre WebSocket e HTTP polling, de acordo com a demanda do sistema. Em cenários de baixa atividade ou inatividade temporária, adotar polling com <strong>intervalos inteligentes</strong> permite liberar recursos do servidor sem comprometer a funcionalidade. Esse mecanismo de fallback reduz drasticamente o consumo de memória, threads e sockets, especialmente quando milhares de clientes mantêm conexões que transmitem dados com pouca ou nenhuma frequência.</p>



<p>Por outro lado, quando o tráfego se intensifica ou a aplicação exige interações em tempo real — como notificações instantâneas, atualizações de dashboards ou interações colaborativas — a migração para WebSockets pode ser feita sob demanda, reestabelecendo o canal persistente de forma transparente para o usuário. Asssim a aplicação só volta a consumir conexões stateful quando realmente necessário.</p>



<figure class="wp-block-image alignleft size-full is-resized"><img data-dominant-color="222222" data-has-transparency="false" loading="lazy" decoding="async" width="850" height="400" src="https://gago.io/wp-content/uploads/2025/06/image.png" alt="" class="wp-image-20698 not-transparent" style="--dominant-color: #222222; box-shadow:var(--wp--preset--shadow--natural);width:316px;height:auto" srcset="https://gago.io/wp-content/uploads/2025/06/image.png 850w, https://gago.io/wp-content/uploads/2025/06/image-480x226.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 850px, 100vw" /></figure>



<p>Como dizia Milton Friedman, não existe almoço grátis! Não mesmo!</p>



<p>Essa transição inteligente exige um bom design no cliente e no servidor, além de métricas claras para guiar a troca entre os modos. Mais que uma otimização, esse tipo de adaptabilidade pode ser a chave para escalar com eficiência e previsibilidade em ambientes com picos e vales de uso.</p>



<h2 class="wp-block-heading">Conexões stateful exigem testes integrados</h2>



<p>Diferente de uma chamada stateless que é aberta e fechada em milissegundos, uma conexão stateful permanece viva, muitas vezes por minutos ou horas. Isso exige um entendimento profundo do ciclo de vida da conexão: como ela é aberta, mantida, recuperada após falhas e finalmente encerrada.</p>



<p>Testes integrados em cenários reais (incluindo falhas de rede, interrupções abruptas e reconexões) são essenciais para garantir que seu sistema não entre em estados inconsistentes ou consuma recursos indefinidamente.</p>



<p>TestContainers podem ajudar nessa jornada, tenho usado com sucesso para a implementação de testes integrados com Postgres e RabbitMQ, por exemplo.</p>



<h2 class="wp-block-heading">A armadilha do consumo invisível de recursos</h2>



<p>Conexões stateful consomem recursos continuamente: <strong><span style="color: #ff0000;" class="stk-highlight">file descriptors, memória, CPU e slots de thread ou evento</span></strong>. Recursos limitado que, quando chegam ao seu limite produzem negação de serviço. Ou seja, demandam escala para que isso não ocorra. Se você não tem mecanismos de timeout, controle de conexões inativas, trocad e protocolo ou desconexão ativa, seu sistema inevitavelmente demandará <strong><span style="color: #ff0000;" class="stk-highlight">escala precipitada</span></strong>, ou entrará em <strong><span style="color: #ff0000;" class="stk-highlight">colapso sob carga</span></strong>.</p>



<p>Ao contrário do modelo stateless, onde cada interação é curta e previsível, as conexões persistentes criam um estado implícito no servidor. Isso limita drasticamente sua capacidade de escalar.</p>



<h2 class="wp-block-heading">A complexidade da reconexão e do roteamento em ambientes orquestrados</h2>



<p>Em ambientes baseados em containers, como Docker e Kubernetes, a expectativa de alta disponibilidade esconde frequentemente a complexidade envolvida na reconexão de clientes após falhas. Quando um pod morre ou um container é reiniciado, qualquer conexão stateful é perdida, exigindo que o cliente detecte a queda, implemente lógica de reconexão e restabeleça o estado — tudo isso sem afetar a experiência do usuário.</p>



<p>Além disso, mesmo com load balancers e service meshes sofisticados, como os baseados em Envoy, não há garantias de que o cliente será reconectado à mesma instância anterior, até porque ela pode nem existir mais. Para aplicações que mantêm estado local, como sessões em memória ou buffers de streaming, isso pode ser peculiarmente desafiador, gerando inconsistência, perda de dados ou necessidade de reprocessamento.</p>



<p>Projetar para esse cenário exige estratégia: </p>



<ul class="wp-block-list">
<li>adoção de stores distribuídos para estado (redis é uma solução possível)</li>



<li>uso de sticky sessions (com moderação)</li>



<li>e principalmente, preparar o cliente para falhas inevitáveis no ciclo de vida dos pods e containers.</li>
</ul>



<p class="has-medium-font-size"><strong><em><span style="color: #ff0000;" class="stk-highlight">A certeza que podemos ter é a de que falhará, portanto, precisamos tratar adequadamente esse comportamento esperado, previsível e natural de uma conexão stateful.</span></em></strong></p>



<h2 class="wp-block-heading">Conclusão</h2>



<p>Conexões de longa duração oferecem vantagens significativas: baixa latência, bidirecionalidade e eficiência. Mas elas também trazem riscos sûtis, que se manifestam tardiamente em produção.</p>



<p>Ao lidar com sistemas distribuídos e de alto desempenho, precisamos projetar com consciência. Cada conexão é uma promessa de recursos alocados. E promessas não cumpridas, cedo ou tarde, cobram seu preço.</p>



<p class="has-text-align-right"><strong><em>Esse post foi baseado em experiências recentes </em></strong><br /><strong><em>em algumas das maiores instituições financeiras do país.</em></strong></p>The post <a href="https://gago.io/blog/perigo-conexoes-stateful/">O perigo silencioso das conexões stateful: por que respeitar o ciclo de vida importa?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/perigo-conexoes-stateful/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>O marketing dos benchmarks</title>
		<link>https://gago.io/blog/benchmarks-mentem/</link>
					<comments>https://gago.io/blog/benchmarks-mentem/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Fri, 28 Mar 2025 21:14:36 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[Software Architecture]]></category>
		<category><![CDATA[Solution Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20646</guid>

					<description><![CDATA[<p>Já faz algum tempo que os benchmarks deixaram de ser apenas uma ferramenta técnica e passaram a ocupar espaço central no marketing de produtos e tecnologias. De linguagens de programação a frameworks web, de modelos de LLM a placas de vídeo, benchmarks são utilizados como argumentos de venda — e com razão: números impressionam. Mas [&#8230;]</p>
The post <a href="https://gago.io/blog/benchmarks-mentem/">O marketing dos benchmarks</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Já faz algum tempo que os benchmarks deixaram de ser apenas uma ferramenta técnica e passaram a ocupar espaço central no marketing de produtos e tecnologias. De linguagens de programação a frameworks web, de modelos de LLM a placas de vídeo, benchmarks são utilizados como argumentos de venda — e com razão: números impressionam.</p>



<p>Mas como todo dado isolado, os benchmarks contam apenas parte da história. Os números são reais, mas o contexto em que esses números são obtidos está longe da realidade da maioria dos sistemas em produção. E é justamente aí que mora o problema.</p>



<span id="more-20646"></span>



<p>Quando o assunto é desenvolvimento, em muitos desses testes, o cenário se resume a algo extremamente simplificado: processar uma string, retornar um JSON estático, ou calcular algo em memória — tudo isso sem envolver banco de dados, sem serialização real, sem cache, sem autenticação, sem concorrência real.</p>



<p>Esses benchmarks não refletem o mundo real.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">O que acontece no mundo real?</h2>



<p>No mundo real, um request HTTP passa por diversas camadas:</p>



<ul class="wp-block-list">
<li>Autenticação e autorização (muitas vezes com JWTs, que exigem criptografia)</li>



<li>Desserialização do payload recebido</li>



<li>Regras de negócio, que frequentemente acessam banco de dados ou serviços externos</li>



<li>Escrita em cache ou leitura de cache (Redis, por exemplo)</li>



<li>Log, tracing, métricas e demais elementos de observabilidade</li>



<li>Processamento concorrente, locks e contenção</li>
</ul>



<p>Tudo isso consome tempo — e não são microssegundos. Estamos falando de milissegundos, muitas vezes dezenas deles. Um simples acesso ao banco pode levar 5, 10, 20ms. Um serviço externo pode adicionar 100ms ou mais de latência.</p>



<blockquote class="wp-block-stackable-blockquote stk-block-blockquote stk-block stk-1d93f25 stk-block-background is-style-default stk--has-background-overlay" data-v="2" data-block-id="1d93f25"><style>.stk-1d93f25 {background-color:linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%) !important;margin-bottom:25px !important;transform:rotate(-6.7deg) scale(0.7) !important;}.stk-1d93f25:before{background-image:linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%) !important;}</style><div class="has-text-align-left stk-block-blockquote__content stk-container stk-1d93f25-container stk-hover-parent"><div class="stk-block-content stk-inner-blocks">
<div class="wp-block-stackable-icon stk-block-icon stk-block stk-6cguppb" data-block-id="6cguppb"><span class="stk--svg-wrapper"><div class="stk--inner-svg"><svg style="height:0;width:0"><defs><lineargradient id="linear-gradient-6cguppb" x1="0" x2="100%" y1="0" y2="0"><stop offset="0%" style="stop-opacity:1;stop-color:var(--linear-gradient-d-246-f-08-color-1)"></stop><stop offset="100%" style="stop-opacity:1;stop-color:var(--linear-gradient-d-246-f-08-color-2)"></stop></lineargradient></defs></svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" aria-hidden="true" width="32" height="32"><path d="M19.8 9.3C10.5 11.8 4.6 17 2.1 24.8c2.3-3.6 5.6-5.4 9.9-5.4 3.3 0 6 1.1 8.3 3.3 2.2 2.2 3.4 5 3.4 8.3 0 3.2-1.1 5.8-3.3 8-2.2 2.2-5.1 3.2-8.7 3.2-3.7 0-6.5-1.2-8.6-3.5C1 36.3 0 33.1 0 29 0 18.3 6.5 11.2 19.6 7.9l.2 1.4zm26.4 0C36.9 11.9 31 17 28.5 24.8c2.2-3.6 5.5-5.4 9.8-5.4 3.2 0 6 1.1 8.3 3.2 2.3 2.2 3.4 4.9 3.4 8.3 0 3.1-1.1 5.8-3.3 7.9-2.2 2.2-5.1 3.3-8.6 3.3-3.7 0-6.6-1.1-8.6-3.4-2.1-2.3-3.1-5.5-3.1-9.7 0-10.7 6.6-17.8 19.7-21.1l.1 1.4z"></path></svg></div></span></div>



<div class="wp-block-stackable-text stk-block-text stk-block stk-khydu0b" data-block-id="khydu0b"><style>.stk-khydu0b .stk-block-text__text{font-size:36px !important;line-height:1em !important;}@media screen and (max-width: 1023px){.stk-khydu0b .stk-block-text__text{font-size:36px !important;}}</style><p class="stk-block-text__text">Decidir adoção ou migração de uma tecnologia baseando-se em menchmarks sintéticos é como escolher um carro pela velocidade de abertura da porta do carona.</p></div>
</div></div></blockquote>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Os percentuais escondem os valores reais&#8230;</h2>



<p>Se um <strong><span style="color: #ff0000;" class="stk-highlight">framework A</span></strong> responde uma requisição simples em <strong>400 microssegundos</strong> e o <strong><span style="color: #ff0000;" class="stk-highlight">framework B</span></strong> em <strong>800 microssegundos</strong>, isso pode parecer uma diferença substancial, afinal um é 100% mais lento que o outro, ou talvez você prefira pensar que um deles é 2 vezes mais rápido, certo? </p>



<p>Mas se a requisição real do seu sistema leva 120ms para ser processada por conta de chamadas externas, então a diferença entre A e B representa menos de 0,5% do tempo total. <strong><span style="color: #ff0000;" class="stk-highlight">Ou seja, menos da metade de 1%.</span></strong></p>



<p>Em uma relação desse tipo temos:</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-gallery has-nested-images columns-default wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-full"><img data-dominant-color="3f84b2" data-has-transparency="true" style="--dominant-color: #3f84b2;" loading="lazy" decoding="async" width="665" height="1000" data-id="20660" src="https://gago.io/wp-content/uploads/2025/03/image-7.png" alt="" class="wp-image-20660 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/03/image-7.png 665w, https://gago.io/wp-content/uploads/2025/03/image-7-480x722.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 665px, 100vw" /></figure>



<figure class="wp-block-image size-large"><img data-dominant-color="302c28" data-has-transparency="true" style="--dominant-color: #302c28;" loading="lazy" decoding="async" width="1024" height="576" data-id="20659" src="https://gago.io/wp-content/uploads/2025/03/image-6-1024x576.png" alt="" class="wp-image-20659 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/03/image-6-1024x576.png 1024w, https://gago.io/wp-content/uploads/2025/03/image-6-980x551.png 980w, https://gago.io/wp-content/uploads/2025/03/image-6-480x270.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>
</figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Uma substituição, em nome do desempenho, pode parecer justificável ao olhar desatento, mas seria como otimizar o tempo de abrir a porta do carro para ganhar desempenho em uma corrida de 10 horas. Sim, pode fazer diferença em cenários extremos e altamente otimizados. Mas para 99.9% dos sistemas em produção, isso é irrelevante.</p>



<figure class="wp-block-image aligncenter size-full"><img data-dominant-color="f39a76" data-has-transparency="true" style="--dominant-color: #f39a76;" loading="lazy" decoding="async" width="441" height="494" src="https://gago.io/wp-content/uploads/2025/03/image-8.png" alt="" class="wp-image-20665 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/03/image-8.png 441w, https://gago.io/wp-content/uploads/2025/03/image-8-268x300.png 268w" sizes="(max-width: 441px) 100vw, 441px" /></figure>



<p>Aqui nos deparamos com o dilema da relevância de tal mudança. Um ganho tão pequeno não justifica, de forma consistente, decisão alguma na maioria absoluta dos casos. Estamos diante de um ganho irrisório, marginal, ínfimo.</p>



<h2 class="wp-block-heading">Então por isso não devemos mudar?</h2>



<p>Você pode encontrar diversos outros argumentos válidos, faça o exercício de buscá-los. É possível e provável que encontre argumentos mais atraentes e que justifiquem uma mudança.</p>



<p>Respondendo à pergunta original, &#8220;Então por isso não devemos mudar?&#8221;, onde &#8220;isso&#8221; é um ganho de desempenho de menos de 0.5%, a resposta é simples: <strong>Não há plausibilidade em realizar mudanças quando o ganho é tão marginal.</strong></p>



<h2 class="wp-block-heading">O que realmente importa?</h2>



<p>Sob a ótica de uma empresa com fins lucrativos, o que importa é o lucro.</p>



<p>E isso pode ser obtido otimizando e maximizando o consumo de recursos com mais eficiência, mas também com a redução de custo. Ou trazendo mais receita, algo que contribua na margem final.</p>



<p>Portanto:</p>



<ul class="wp-block-list">
<li><strong>Latência total da requisição</strong>
<ul class="wp-block-list">
<li>Aumento da eficiência.</li>



<li>Aumento da percepção do seu usuário.</li>



<li>Redução de custos.</li>
</ul>
</li>



<li><strong>Capacidade de escalar horizontalmente</strong>
<ul class="wp-block-list">
<li>Aumento da eficiência</li>



<li>Capacidade de atender mais clientes com custo otimizado para a demanda.</li>
</ul>
</li>



<li><strong>Facilidade de observabilidade</strong>
<ul class="wp-block-list">
<li>Menor tempo até a descoberta de bugs</li>



<li>Menor tempo reprodução e de resolução de bugs.</li>



<li>Melhoria na percepção do usuário final</li>



<li>Permite ser proativo na correção de bugs</li>
</ul>
</li>



<li><strong>Custo de manutenção</strong>
<ul class="wp-block-list">
<li>Redução de tempo para entregar novas features</li>



<li>Redução de tempo para corrigir bugs</li>



<li>Redução dos skills de contratação</li>
</ul>
</li>



<li><strong>Confiabilidade e estabilidade</strong>
<ul class="wp-block-list">
<li>Redução do esforço de operações para suportar a aplicação.</li>



<li>Redução das interrupções nos times de desenvolvimento para correção de bugs.</li>



<li>Redução da frustração do cliente.</li>



<li>Aumento da percepção de qualidade pelo cliente.</li>
</ul>
</li>
</ul>



<p>Quando falamos de benefícios técnicos, precisamos ter em mente que queremos:</p>



<ul class="wp-block-list">
<li>Mais clientes.</li>



<li>Por mais tempo.</li>



<li>Pagando mais durante toda a vida dele no seu software.</li>



<li>Gastando menos para produzir e manter o software e infraestrutura.</li>
</ul>



<p>E muitas vezes, para que esses objetivos sejam alcançados, empresas fazem um zilhão de coisas para criar um ambiente legal para os devs, para contratar, para reter talentos e também seus clientes.</p>



<p class="has-medium-font-size"><strong><span style="color: #ff0000;" class="stk-highlight">Entenda a regra do jogo, para entender quais são os melhores argumentos.</span></strong></p>



<h2 class="wp-block-heading">Conclusão</h2>



<p>Benchmarks sintéticos servem para comparar aspectos específicos, mas não devem <strong><span style="color: #ff0000;" class="stk-highlight">guiar decisões arquiteturais sem contexto</span></strong>. </p>



<p class="has-medium-font-size">No mundo real, a performance relevante é medida <strong><span style="color: #ff0000;" class="stk-highlight">de ponta a ponta</span></strong>, em cenários reais, sob carga real.</p>



<p>Antes de trocar de framework por uma diferença de microssegundos ou nanosegundos, pergunte-se: qual será o ganho real? E o que vai realmente mover o ponteiro da performance no meu sistema? </p>



<p><span style="color: #ff0000;" class="stk-highlight"><strong>Será que não há outros argumentos mais interessantes que foram ignorados?</strong></span></p>



<p>No fim das contas, escolher um framework apenas por benchmarks é como escolher um carro olhando apenas o velocímetro, ignorando conforto, segurança, consumo e confiabilidade. </p>



<p>A performance importa, mas no contexto certo.</p>The post <a href="https://gago.io/blog/benchmarks-mentem/">O marketing dos benchmarks</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/benchmarks-mentem/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Mitigando os custos de Reflection</title>
		<link>https://gago.io/blog/2025-03-reflection-cache/</link>
					<comments>https://gago.io/blog/2025-03-reflection-cache/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Tue, 18 Mar 2025 20:23:04 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Desenvolvimento]]></category>
		<category><![CDATA[Oragon Architecture]]></category>
		<category><![CDATA[.NET Core]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Dependency Injection]]></category>
		<category><![CDATA[Oragon.RabbitMQ]]></category>
		<category><![CDATA[RabbitMQ]]></category>
		<category><![CDATA[Reflection]]></category>
		<category><![CDATA[Software Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20615</guid>

					<description><![CDATA[<p>Reflection oferece um mecanismo sofisticado para inspeção e manipulação de metadados de tipos, métodos e propriedades em tempo de execução. No entanto, essa flexibilidade vem acompanhada de custos elevados, que penalizam desempenho e eficiência de qualquer aplicação. Sempre que podemos evitar, evitamos seu uso, mas e quando não podemos? Como podemos tratar ou mitigar esses [&#8230;]</p>
The post <a href="https://gago.io/blog/2025-03-reflection-cache/">Mitigando os custos de Reflection</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Reflection oferece um mecanismo sofisticado para inspeção e manipulação de metadados de tipos, métodos e propriedades em tempo de execução. No entanto, essa flexibilidade vem acompanhada de custos elevados, que penalizam desempenho e eficiência de qualquer aplicação.</p>



<p>Sempre que podemos evitar, evitamos seu uso, mas e quando não podemos? Como podemos tratar ou mitigar esses prejuízos em desempenho?</p>



<p>Hoje vou apresentar uma abordagem que usei recentemente em um projeto recente.</p>



<span id="more-20615"></span>



<h2 class="wp-block-heading">Reflection e seus custos</h2>



<p>O impacto do uso de Reflection no desempenho pode ser atribuído a diversos fatores, sendo os principais:</p>



<ol class="wp-block-list">
<li><strong>Sobrecarga Computacional e Latência</strong>: O uso de Reflection exige buscas dinâmicas por metadados no assembly, além de processos de validação de acesso e resolução dinâmica de métodos. Esse mecanismo pode ser entre 10 e 100 vezes mais lento do que chamadas diretas a métodos, o que pode impactar a responsividade da aplicação em cenários de alta concorrência.</li>



<li><strong>Alocação Excessiva de Memória</strong>: Reflection frequentemente induz a criação de objetos temporários, principalmente ao utilizar <code>Activator.CreateInstance</code> para instanciar dinamicamente classes, o que resulta em maior pressão sobre o garbage collector e consequente degradação de desempenho.</li>



<li><strong>Impedimentos à Otimização Just-In-Time (JIT)</strong>: Métodos invocados via Reflection não são otimizados pelo compilador JIT, pois a inferência estática é impossibilitada. Como resultado, essas chamadas não aproveitam otimizações como inlining e caching de código compilado.</li>



<li><strong>Redução da Capacidade de Diagnóstico e Debugging</strong>: Reflection pode tornar o código menos previsível, dificultando a rastreabilidade e depuração de erros, uma vez que falhas podem ocorrer apenas em tempo de execução e não serem capturadas pelo compilador.</li>



<li><strong>Impacto na Segurança e Manutenção do Código</strong>: O uso excessivo de Reflection pode dificultar a manutenção do código, especialmente em equipes grandes, pois oculta dependências explícitas e torna a lógica de negócio mais difícil de compreender e auditar.</li>
</ol>



<h2 class="wp-block-heading">Se é tão ruim, por que não abolimos o uso de reflection?</h2>



<p>Abolir é um termo muito duro para o problema, mas sim evitar o uso de reflection é uma estratégia que começou a ser adotada por volta de 2005 e é utilizada até hoje. </p>



<p>Evitamos reflection sempre que possível, como no caso que trago hoje para o exemplo: </p>



<p>As versões pré-lançamento do <strong>Oragon.RabbitMQ</strong> eram baseadas em lambdas com assinaturas pré-definidas, de forma que não precisava de reflection em momento algum.  Forçar uma assinatura única, para evitar o uso de reflection, não era meu desejo, mas era o que dava para fazer para evitar reflection.</p>



<p>Somente em dezembro de 2024, em uma mentoria (não me lembro se foi com os alunos do Cloud Native .NET ou do Mensageria .NET) que, durante uma explicação, resolvi olhar a implementação de Minimal API&#8217;s do ASP .NET com o <strong>ILSpy</strong>, e entender por baixo do capô como Minimal API&#8217;s faziam, que me toquei de que não tinha como evitar o uso de reflection.</p>



<p>E de fato, em certos tipos de cenário, não dá para evitar reflection.</p>



<h2 class="wp-block-heading">Situações em que Reflection é inevitável</h2>



<p>Há casos em que simplesmente não é possível adotar outra abordagem, e seu emprego é indispensável e, em alguns poucos casos, reflection é a única solução viável:</p>



<ul class="wp-block-list">
<li><strong>Serialização/Desserialização Dinâmica</strong>: Frameworks de serialização, como Newtonsoft.Json e System.Text.Json, utilizam Reflection para inspecionar propriedades de classes e converter dados entre diferentes formatos.</li>



<li><strong>Injeção de Dependência e Configuração de Serviços</strong>: Contêineres de injeção de dependência, como Autofac e o próprio <code>IServiceProvider</code> do ASP.NET Core, utilizam Reflection para resolver instâncias de serviços e gerar fábricas dinamicamente.</li>



<li><strong>Execução de Código Dinâmico</strong>: Aplicações que requerem carregamento dinâmico de assemblies e execução de código em tempo de execução, como sistemas de plugins e motores de scripts, dependem da capacidade introspectiva da Reflection.</li>



<li><strong>Automação e Ferramentas de Análise Estática</strong>: Ferramentas de análise de código, geração de proxies dinâmicos e introspecção de assemblies fazem uso extensivo de Reflection para examinar a estrutura interna de classes e métodos.</li>
</ul>



<h2 class="wp-block-heading">Source Generator é uma saída elegante</h2>



<p>Lançado em 2020 com o .NET 5, Source Generator <em><a href="https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/" target="_blank" rel="noopener" title="um novo recurso do compilador C# que permite que desenvolvedores C# inspecionem o código do usuário e gerem novos arquivos de origem C# que podem ser adicionados a uma compilação.">um novo recurso do compilador C# que permite que desenvolvedores C# inspecionem o código do usuário e gerem novos arquivos de origem C# que podem ser adicionados a uma compilação.</a></em>. Source Generator não é bala de prata e não substitui completamente Reflection em todos os cenários, mas há uma parte significativa dos cenários de uso de reflection, em que é possível trazer toda a introspecção e dinamismo, que seria executada em runtime, para o momento do build.</p>



<p>Muitas vezes reflection é utilizada para simplificar e facilitar a codificação. Por exemplo, quando você constrói uma Action (Método) em uma Controller ou define uma expressão em uma Minimal API você está usando essa simplificação. O intuito é reduzir a quantidade de código protocolar e deixar para a infraestrutura do ASP.NET realizar todo o bind entre o model, o mecanismo de injeção de dependência e seu método.</p>



<p>Mas concorda que cada controller ou minimal API terá uma única mesma assinatura durante o ciclo de vida da instância da aplicação? A assinatura não é dinâmica, é estática. Para mudar uma assinatura do método, demanda um build, deploy etc. </p>



<p>Então, por que usar reflection em tempo de execução se podemos fazer isso apenas durante o build?</p>



<p>Essa é uma pergunta para a qual Source Generator é a resposta.</p>



<p>Assim, é perfeitamente possível ter source generators que convertam minimal api&#8217;s e actions de controllers em métodos mais burocráticos, removendo toda necessidade de reflection, trazendo todo o custo para a build. Essa abordagem antecipa a introspecção para o momento do build, assegurando que não precisaremos de introspecção durante a execução da aplicação (runtime).</p>



<p>Enquanto na Microsoft a busca por AOT faz a adoção de Source Generators estar aceleradíssima e abrangente, na comunidade e para os produtores de bibliotecas essa ainda não é uma realidade.</p>



<p>Bibliotecas como:</p>



<ul class="wp-block-list">
<li>MassTransit</li>



<li>EasyNetQ</li>



<li>Oragon.RabbitMQ</li>
</ul>



<p>Ainda não adotaram Source Generators e é possível que outras bibliotecas sequer façam algum dia, sendo incompatíveis com AOT.</p>



<h2 class="wp-block-heading">Mas afinal que custo é esse?</h2>



<p>Quando trabalhamos com Middlewares, somos forçados a implementar uma assinatura específica, assim evitamos a utilização de reflection, mas quando lidamos com Controllers, Actions e Minimal API&#8217;s, há certa liberdade na assinatura e reflection se faz necessário.</p>



<p>Quando você implementa uma Minimal API ou uma Controller, um mecanismo do ASP.NET precisa mapear uma rota (em geral, informada via atributo) para um método escrito pelo desenvolvedor, do qual o ASP.NET não reconhece a assinatura. Esse método, ou seja, o código que você escreveu, pode ter praticamente qualquer assinatura.</p>



<p>Assim, o ASP.NET precisa transformar um request HTTP em parâmetros para que consiga chamar o seu código (da Action, por exemplo), e para isso ele vai pesquisar todos os argumentos do método, de todos os métodos públicos, de todas as suas controllers e minimal API&#8217;s em busca das assinaturas de cada método. Uma vez de posse dessa lista de assinaturas, é possível determinar os ModelBinders para cada Action e realizar algumas validações em busca de coerência e consistência.</p>



<p>Dessa forma, o ASP.NET consegue dinamicamente chamar seu método, passando Body, Http Headers, forms, serviços configurados na injeção de dependência e tudo mais que seu método precisa.</p>



<p>Sempre que precisamos perguntar para um tipo quais são suas características, pagamos um preço alto. Se queremos descobrir quais são as propriedades de um tipo, quais são os parâmetros de um método, ou coisas similares, temos de usar reflection para isso.</p>



<p class="has-text-align-right has-small-font-size">Esse não é o único cenário de reflection, é um exemplo.</p>



<h2 class="wp-block-heading">Então a solução não seria abolir?</h2>



<p>De um lado, reflection nos permite a injeção de dependência como conhecemos hoje, actions e controllers, minimal API&#8217;s e abstrações de consumo de filas como <strong>MassTransit</strong>, <strong>EasyNetQ</strong> e <strong>Oragon.RabbitMQ</strong>, da mesma forma que nos permite a adoção de MediatR e outras muitas tecnologias, inclusive de ORM.</p>



<p>Então, conviver com .NET sem reflection tornaria a plataforma irreconhecível, muito mais burocrática, tediosa e complexa.</p>



<p>As alternativas são:</p>



<ul class="wp-block-list">
<li>De um lado, adotar source generators sempre que possível e viável. </li>



<li>E, de outro lado, otimizar o uso de reflection, evitando introspecções desnecessárias.</li>
</ul>



<h2 class="wp-block-heading">Otimizando Reflection em runtime com cache</h2>



<p>Uma das demandas para o Oragon.RabbitMQ é a adoção de Source Generators para podermos eliminar o uso de reflection em runtime. Mas assim como outras bibliotecas desse gênero, essa solução ainda não está pronta.</p>



<p class="has-text-align-right has-small-font-size"><em>Não busquei no roadmap das demais,</em><br /><em> apenas busquei implementações </em><br /><em>de source generators e reflection</em><br /><em> na data desse post</em></p>



<p>Então, como minimizar o impacto de reflection em meu código?</p>



<p>A primeira pergunta a ser realizada antes de pensarmos em otimização é:<br /><span style="color: #ff0000;" class="stk-highlight">Quantas vezes serão realizadas essas introspecções?</span></p>



<p>A forma mais comum de implementação desse tipo de introspecção é realizar a cada chamada dinâmica. <br /><span style="color: #ff0000;" class="stk-highlight">Essa é a forma mais fácil e também a mais <strong>ineficiente</strong>.</span></p>



<p>A <span style="color: #ff0000;" class="stk-highlight">solução mais eficiente</span> seria não adotar reflection, mas uma vez necessária, <span style="color: #ff0000;" class="stk-highlight">a solução mais eficiente</span> é produzir um cache otimizado, reduzindo drasticamente o custo e pagando esse preço apenas uma vez, deixando de executar a introspecção a toda chamada dinâmica para pagar esse preço apenas uma única vez, necessariamente na subida da aplicação.</p>



<p>Temos como solução:</p>



<ul class="wp-block-list">
<li>A redução da quantidade de introspecção.</li>



<li>Movemos todo o custo para a inicialização da aplicação.</li>



<li>Cada chamada dinâmica não precisa mais de introspecção.</li>



<li>Mover para a inicialização da aplicação evita custo exorbitante na primeira execução, como seria natural caso precisasse da primeira chamada para ativar esse cache.</li>
</ul>



<p>Assim, evitamos buscas repetitivas por métodos, propriedades, parâmetros e atributos, e é possível armazenar esses resultados em estruturas de cache eficientes que duram por todo o ciclo de vida da instância da aplicação.</p>



<p>Essa estratégia reduz significativamente o tempo gasto na recuperação de metadados, melhorando a eficiência global.</p>



<h2 class="wp-block-heading">Mas como implementar?</h2>



<p>Ao invés de, ao receber cada mensagem, descobrir como chamar o método dinamicamente, optamos por uma estratégia em que, na subida da aplicação validamos tudo que foi registrado.</p>



<p>E talvez você não tenha percebido que ao chamar app.MapPost, você está registrando um handler na infra do ASP.NET Minimal API.</p>



<p>Assim quando você chama:</p>



<ul class="wp-block-list">
<li>No caso do ASP.NET minimal API:
<ul class="wp-block-list">
<li>MapGet</li>



<li>MaoPost</li>



<li>MapGet</li>



<li>MapPatch</li>



<li>MapPut</li>



<li>MapDelete</li>



<li>&#8230;</li>
</ul>
</li>



<li>Ou no caso do Oragon.RabbitMQ:
<ul class="wp-block-list">
<li>MapQueue</li>
</ul>
</li>
</ul>



<p>Você está registrando listeners, e parte do que você registra é essa expressão, que durante a compilação é encarada como um método.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="MapPost (ASP.NET Minimal API)" data-enlighter-group="Exemplo1">...
app.MapPost("/http-example", ([FromServices] EmailService svc, [FromBody] DoSomethingCommand cmd)
=> {
    ...
    return Results.Ok();
});
...</pre>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="MapQueue (Oragon.RabbitMQ)" data-enlighter-group="Exemplo1">...
app.MapQueue("amqp-example", ([FromServices] EmailService svc, [FromBody] DoSomethingCommand cmd)
=> {
    ...
    return AmqpResults.Ack();
})
...</pre>



<p>Nem <strong>ASP.NET Minimal API</strong>, nem o <strong>Oragon.RabbitMQ</strong> precisamos ler o assembly inteiro buscando itens de interesse. Nós já recebemos essas informações gratuitamente quando no program.cs as chamadas ao MapPost (e seus irmãos) e MapQueue são executadas para registrar esses handlers. Então, ainda durante o processo de configuração da aplicação, já recebemos tudo que precisamos para fazer todas as análises.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="Extension Methods" data-enlighter-group="">...

public static ConsumerParameters MapQueue(this IHost host, string queueName, Delegate handler)
{
	return host.Services.MapQueue(queueName, handler);
}

...

public static ConsumerDescriptor MapQueue(this IServiceProvider serviceProvider, string queueName, Delegate handler)
{
	var consumerDescriptor = new ConsumerDescriptor(serviceProvider, queueName, handler);

	ConsumerServer consumerServer = serviceProvider.GetRequiredService&lt;ConsumerServer>();

	consumerServer.AddConsumerDescriptor(consumerDescriptor);

	return consumerDescriptor;
}

...</pre>



<p>Após a configuração, temos a subida da aplicação, e é aí que fazemos uso dessas configurações para de fato produzir análises com base em tudo que foi registrado.</p>



<p>Na hora de subir o consumidor, é hora de varrer os metadados internos, que temos com a assinatura de cada endpoint e buscar todos os parâmetros.</p>



<p>Com base na lista de parâmetros de entrada e saída, constrói-se o model binder com a lista de binders que preencherão os argumentos daquele método. </p>



<p>Pronto, agora a cada execução você não precisa descobrir quais são os argumentos do método dinâmico, você apenas obtém o model binder correspondente, itera produzindo a lista de argumentos e produz essa chamada.</p>



<h2 class="wp-block-heading">Benefícios</h2>



<p>A implementação das técnicas acima proporciona uma série de vantagens:</p>



<ol class="wp-block-list">
<li><strong>Melhoria no Tempo de Resolução de Métodos</strong>: A redução do número de chamadas de Reflection permite ganhos significativos de tempo de execução.</li>



<li><strong>Eficiência na Utilização de Memória</strong>: O uso de caches reduz a alocação desnecessária de objetos intermediários.</li>



<li><strong>Escalabilidade Aprimorada</strong>: Aplicações de alta carga se beneficiam da redução da latência de chamadas dinâmicas, tornando-se mais responsivas e eficientes.</li>



<li><strong>Maior Clareza e Manutenção do Código</strong>: Reduzindo a dependência de Reflection, o código se torna mais previsível e fácil de manter.</li>



<li><strong>Segurança Aprimorada</strong>: Evitar Reflection excessiva reduz vulnerabilidades associadas a ataques baseados em introspecção de código e manipulação dinâmica de objetos.</li>
</ol>



<h2 class="wp-block-heading">Considerações Finais</h2>



<p>Reflection, apesar de seu custo, continua sendo um recurso fundamental para determinados cenários. A aplicação de técnicas como caching de metadados e geração estática de código reduzem significativamente os impactos negativos associados ao seu uso. Portanto, em vez de evitá-la completamente, a abordagem correta é otimizar sua utilização, garantindo que a flexibilidade oferecida pela introspecção de código não comprometa a escalabilidade e o desempenho da aplicação.</p>The post <a href="https://gago.io/blog/2025-03-reflection-cache/">Mitigando os custos de Reflection</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/2025-03-reflection-cache/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>O custo real das exceptions – 02 –  Estamos olhando para o lugar certo?</title>
		<link>https://gago.io/blog/custo-real-das-exceptions-02-escala/</link>
					<comments>https://gago.io/blog/custo-real-das-exceptions-02-escala/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Thu, 20 Feb 2025 03:38:28 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Desenvolvimento]]></category>
		<category><![CDATA[exceptions]]></category>
		<category><![CDATA[notification pattern]]></category>
		<category><![CDATA[Software Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20501</guid>

					<description><![CDATA[<p>Sem sombra de dúvidas, o principal argumento que sustenta a ideia de que exceptions são ruins é o custo. Afinal, ninguém quer colar em si uma placa dizendo que &#8220;optou pelo caminho mais lento&#8220;. Ao mesmo tempo, as discussões acaloradas transformam o debate em uma rinha onde cada qual adota uma postura que desqualifica toda [&#8230;]</p>
The post <a href="https://gago.io/blog/custo-real-das-exceptions-02-escala/">O custo real das exceptions – 02 –  Estamos olhando para o lugar certo?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Sem sombra de dúvidas, o principal argumento que sustenta a ideia de que exceptions são ruins é o custo. Afinal, ninguém quer colar em si uma placa dizendo que &#8220;<strong>optou pelo caminho mais lento</strong>&#8220;.</p>



<p>Ao mesmo tempo, as discussões acaloradas transformam o debate em uma rinha onde cada qual adota uma postura que desqualifica toda e qualquer opinião contrária.</p>



<p>Mas afinal, quanto custa uma exception?</p>



<p>Este artigo não pretende fornecer respostas definitivas, mas levantar questionamentos importantes: </p>



<p>Será que estamos ignorando alguma peça fundamental nessa discussão? A aversão às exceptions é justificada por dados concretos ou apenas uma tendência emergente sem embasamento empírico suficiente?</p>



<span id="more-20501"></span>



<h2 class="wp-block-heading">Uma análise superficial pode enganar?</h2>



<p>O principal argumento contra exceptions está no seu suposto alto custo computacional. De fato, quando uma exceção é lançada, o runtime do .NET precisa capturar o estado da pilha, criar um objeto de exceção e executar o processo de unwinding da stack até encontrar um manipulador adequado. Sem dúvida, esse overhead é significativamente maior do que o de um simples <code>if/else</code>.</p>



<p class="has-white-color has-vivid-red-background-color has-text-color has-background has-link-color has-medium-font-size wp-elements-577fbc95892a70702896278b36d3121c"><strong>Mas a pergunta fundamental que não estamos fazendo é: </strong></p>



<p class="has-white-color has-vivid-red-background-color has-text-color has-background has-link-color wp-elements-475c00320dfae7d82ddce2afcf8250d7">Que custo é esse? Esse custo realmente importa? Ele é relevante dentro do contexto de aplicações modernas que precisam acessar banco de dados relacional, nosql, cache, consumir mensagens de uma infra de mensageria ou stream, ou chamar apis http/rest, ou enviar logs pela rede opentelemetry?</p>



<h3 class="wp-block-heading">Benchmarks: Comparação com outras operações</h3>



<p>Ao analisar benchmarks, observamos que lançar uma exceção é muitas vezes mais custoso do que adotar if&#8217;s. Esses comparativos estão na casa dos nanosegundos (ns) ou microssegundos (μs). </p>



<p>No entanto, quando comparado a operações triviais em sistemas modernos, esse impacto pode ser marginal:</p>



<ul class="wp-block-list">
<li>Consultas a bancos de dados (~2-5ms);</li>



<li>Acessos a cache distribuído como Redis (~0.5-1ms);</li>



<li>Chamadas a APIs REST (~5-50ms, dependendo da rede e latência);</li>



<li>Processamento de mensagens assíncronas em filas distribuídas (~2-5ms, dependendo da carga do sistema).</li>
</ul>



<p>Em todos os exemplos acima, optei por valores utópicos. No mundo real, dá para multiplicar esses números algumas vezes.</p>



<p>Dado esse cenário, <strong>será que o custo das exceptions realmente justifica evitá-las completamente</strong>? </p>



<ul class="wp-block-list">
<li>A Microsoft, ao projetar o novo e moderno ASP.NET Core e o próprio .NET Runtime, não as removeu. Será que deveríamos?</li>



<li>Qual é o impacto real na execução das aplicações?</li>
</ul>



<p>Talvez uma informação adicional possa nos ajudar a refletir sobre:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Valor</th><th>Em Segundos</th><th>Em Milisegundos</th></tr></thead><tbody><tr><td><strong>1 ns : 1 Nanosegundo</strong></td><td><strong>0,000 000 001 seg</strong></td><td><strong>0,000 001 ms</strong></td></tr><tr><td><strong>1 μs : 1 Microsegundo</strong></td><td><strong>0,000 001 seg</strong></td><td><strong>0,001 ms</strong></td></tr></tbody></table></figure>



<p>Em vez de analisar exceptions apenas sob a ótica da performance comparando com o custo de um simples IF, precisamos contextualizar seu uso dentro do fluxo de execução da aplicação, <strong>entendendo quando seu custo é relevante e quando não é</strong>.</p>



<figure class="wp-block-image size-large"><img data-dominant-color="282a29" data-has-transparency="true" style="--dominant-color: #282a29;" loading="lazy" decoding="async" width="1024" height="467" src="https://gago.io/wp-content/uploads/2025/02/image-8-1024x467.png" alt="" class="wp-image-20543 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/02/image-8-980x447.png 980w, https://gago.io/wp-content/uploads/2025/02/image-8-480x219.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Por exemplo, no vídeo do <strong><em>JUL/2021 &#8211; Nick Chapsas &#8211; O custo oculto das exceções no .NET</em></strong> que referenciei no post anterior, temos:</p>



<ul class="wp-block-list">
<li>Medição em nanosegundos | 1 ns = 0,000 001 ms</li>
</ul>



<figure class="wp-block-image size-large"><img data-dominant-color="080809" data-has-transparency="true" style="--dominant-color: #080809;" loading="lazy" decoding="async" width="1024" height="388" src="https://gago.io/wp-content/uploads/2025/02/image-4-1024x388.png" alt="" class="wp-image-20545 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/02/image-4-1024x388.png 1024w, https://gago.io/wp-content/uploads/2025/02/image-4-980x372.png 980w, https://gago.io/wp-content/uploads/2025/02/image-4-480x182.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>A ausência de latência foi algo que me chamou a atenção em todos os benchmarks. Afinal, qual a relação entre nanosegundo/microssegundo com milissegundos?</p>



<p>Para entender o impacto da latência na expressividade do custo das exceptions resolvi fazer testes adicionando latência. Assim, rodei o benchmark acima por 45h38m, rodando 120 benchmarks diferentes, por todo esse período.</p>



<p>Quando adicionamos 2 milissegundos à execução, temos números mais expressivos, números que fazem mais sentido à compreensão humana.</p>



<p>2 ns = 1 Nanosegundo = 0,000 000 001 seg = 0,000 001 ms</p>



<p>assim</p>



<p>2ms = 2&#8217;000&#8217;000 ns</p>



<p>Assim, podemos ter uma noção melhor de quanto custa uma exception em relação à execução de uma aplicação, que, por exemplo, acesse um banco de dados.</p>



<p class="has-white-color has-text-color has-background has-link-color has-medium-font-size wp-elements-4bbacbda135708556295cd9552d6e7c3" style="background:radial-gradient(rgb(2,3,129) 0%,rgb(9,83,172) 100%)">Trazer os números do teste para a escala dos milissegundos torna o resultado menos abstrato. Agora é quase como se pudéssemos tocá-los.</p>



<p class="has-vivid-red-color has-black-background-color has-text-color has-background has-link-color has-small-font-size wp-elements-f9c10bd07195f06a49432b7fe349d2b8"><strong>Ainda vou detalhar esse teste, sua metodologia, em um post dedicado ao assunto, mas por hora é possível entender que a escala faz total diferença.</strong></p>



<h2 class="wp-block-heading">Será que estamos fazendo as perguntas certas?</h2>



<p class="has-medium-font-size">A questão das exceptions não pode ser reduzida a um debate simples sobre custo, precisamos separar <strong>custo </strong>de <strong>valor</strong>.</p>



<p>Para isso, precisamos fazer perguntas mais profundas:</p>



<ul class="wp-block-list">
<li><strong>O custo computacional das exceptions é realmente significativo? </strong><br /><strong>Em quais cenários?</strong></li>



<li><strong>Até que ponto os benchmarks que desconsideram latência são confiáveis? </strong><br /><strong>Será que eles apresentam uma visão tendenciosa do problema?</strong></li>



<li><strong>Estamos realmente buscando otimização de milésimos ou centésimos de milissegundo enquanto ignoramos impactos na arquitetura do software?</strong></li>



<li><strong>Quais são os trade-offs reais entre código mais legível e pequenas otimizações de desempenho?</strong></li>
</ul>



<p>As críticas às exceptions geralmente nascem do uso inadequado. Como uma faca, que pode ser usada tanto para o bem quanto para o mal, seu mau uso pode gerar problemas reais. </p>



<p class="has-white-color has-vivid-red-background-color has-text-color has-background has-link-color has-medium-font-size wp-elements-ffcbfe735f147427ac11f46f1c24ab51">Mas será que a solução seria simplesmente evitá-las? <br />Ou deveríamos buscar diretrizes mais claras para seu uso eficiente?</p>



<h2 class="wp-block-heading">Próximos passos</h2>



<p>No primeiro post, você viu diversas referências em vídeos que defendem a abolição das exceptions. Neste post, você viu que há uma questão sobre esse argumento central do custo de exceptions, a escala.</p>



<figure class="wp-block-image size-large"><img data-dominant-color="ebeaea" data-has-transparency="true" style="--dominant-color: #ebeaea;" loading="lazy" decoding="async" width="1024" height="1024" src="https://gago.io/wp-content/uploads/2025/02/image-5-1024x1024.png" alt="" class="wp-image-20548 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/02/image-5-980x980.png 980w, https://gago.io/wp-content/uploads/2025/02/image-5-480x480.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>A escala em nanossegundos ou microssegundos é injusta, visto que todas as nossas grandes operações acontecem na escala dos milissegundos.</p>



<p>Dizer que lançar exception custa 10 vezes, custa 30 vezes mais, pode ser um argumento verídico, entretanto perde força quando olhamos para a escala dos milissegundos.</p>



<p class="has-medium-font-size">Ao trocarmos de escala, a tortura estatística derrete.</p>



<p>Da mesma forma que grandes números são inimagináveis.</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Números grandes são inimagináveis #matemática" width="1080" height="608" src="https://www.youtube.com/embed/GZOuaIsXCnQ?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>Pequenos números também são.</p>



<p>Por isso é tão importante trazer para a escala de milissegundos (ms) em vez de continuar em nanossegundos (ns) ou microssegundos (μs). <strong><span style="color: #ff0000;" class="stk-highlight">Mas é preciso muito cuidado para não fazer parecer que não há custo</span></strong>. A questão é sobre qual ponto de referência estamos adotando. Se tomarmos como referência o custo de um if, a exceções são muito mais caras, mas se tomarmos como referência um request http, uma operação maior, esse custo quase desaparece, se torna imperceptível. Só reaparecendo diante de um cenário de múltiplas exceções sendo lançadas na mesma execução, mas aqui o problema não é a exception, mas o tratamento equivocado.</p>



<p>Para essa discussão evoluir, preciso realmente fazer considerações:</p>



<ul class="wp-block-list">
<li>Para que lançamos exceptions</li>



<li>Quando lançamos exceptions</li>



<li>Onde lançamos exceptions, onde não lançamos.</li>



<li>Por que tratamos?</li>



<li>Onde não devemos tratar, devemos deixar estourar.</li>



<li>Como tratamos.</li>



<li>Onde tratamos.</li>



<li>O que é tratar, e o que fazer em cada contexto.</li>
</ul>



<p>e todos os detalhes para assegurar que, ao apresentar o resultado do benchmark, não alimente a ideia de uso indiscriminado e ilimitado de exceptions.</p>



<p>Por isso, o próximo post falará sobre as boas práticas com exceptions.</p>The post <a href="https://gago.io/blog/custo-real-das-exceptions-02-escala/">O custo real das exceptions – 02 –  Estamos olhando para o lugar certo?</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/custo-real-das-exceptions-02-escala/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>O custo real das exceptions &#8211; 01 &#8211; A série</title>
		<link>https://gago.io/blog/custo-real-das-exceptions-01-a-serie/</link>
					<comments>https://gago.io/blog/custo-real-das-exceptions-01-a-serie/#comments</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Thu, 20 Feb 2025 00:15:15 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Desenvolvimento]]></category>
		<category><![CDATA[exceptions]]></category>
		<category><![CDATA[notification pattern]]></category>
		<category><![CDATA[Software Architecture]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20518</guid>

					<description><![CDATA[<p>O uso de exceptions no .NET tem sido um tópico de debates acalorados na comunidade. Para alguns, exceptions representam um custo desnecessário, um vestígio de práticas antiquadas que poderiam ser substituídas por abordagens mais eficientes. Para outros, são um pilar essencial para a robustez e previsibilidade do software moderno. Se exceptions realmente fossem um problema [&#8230;]</p>
The post <a href="https://gago.io/blog/custo-real-das-exceptions-01-a-serie/">O custo real das exceptions – 01 – A série</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>O uso de exceptions no .NET tem sido um tópico de debates acalorados na comunidade. Para alguns, exceptions representam um custo desnecessário, um vestígio de práticas antiquadas que poderiam ser substituídas por abordagens mais eficientes. Para outros, são um pilar essencial para a robustez e previsibilidade do software moderno.</p>



<p>Se exceptions realmente fossem um problema significativo, por que ainda são adotadas de forma unilateral, em todo lugar, principalmente nos componentes centrais do ecossistema .NET, no ASP.NET, no Entity Framework e em diversas bibliotecas e fundações da própria Microsoft? </p>



<p>Será que há questões que não estamos considerando? </p>



<p>Antes de abraçar qualquer lado, é fundamental analisar a questão de forma técnica e pragmática.</p>



<p>Esta série de artigos pretende fornecer respostas, e levantar questionamentos importantes.</p>



<span id="more-20518"></span>



<h2 class="wp-block-heading">Em 2019&#8230;</h2>



<p>Em 2016~2019 uma onda de &#8220;ataque às exceptions&#8221; apareceu na comunidade. Posts e mais posts falando mal de exceptions.</p>



<p>Até então, tudo bem, se não fossem os argumentos para trocar exceptions por notificações.</p>



<p>Foi então que resolvi parar para escrever sobre o assunto em um dos posts que mais rendeu aqui no gaGO.io: </p>



<h2 class="wp-block-heading has-text-align-center">Notification Pattern – Estão te vendendo um conceito errado</h2>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="843" height="474" src="https://gago.io/wp-content/uploads/2019/04/notification-pattern.png" alt="" class="wp-image-6454" srcset="https://gago.io/wp-content/uploads/2019/04/notification-pattern.png 843w, https://gago.io/wp-content/uploads/2019/04/notification-pattern-300x169.png 300w" sizes="(max-width: 843px) 100vw, 843px" /></figure>



<p class="has-text-align-center">Notification Pattern prevê uma forma permissiva que troca exceptions por notificações em um determinado contexto, no entanto embora a maioria das publicações a respeito falem em não lançar exceptions, veremos que isso não é bem assim.</p>



<div class="wp-block-uagb-buttons uagb-buttons__outer-wrap uagb-btn__default-btn uagb-btn-tablet__default-btn uagb-btn-mobile__default-btn uagb-block-803f4c7f"><div class="uagb-buttons__wrap uagb-buttons-layout-wrap ">
<div class="wp-block-uagb-buttons-child uagb-buttons__outer-wrap uagb-block-a7788e0a wp-block-button"><div class="uagb-button__wrapper"><a class="uagb-buttons-repeater wp-block-button__link" aria-label="" href="/blog/2019-04-28-notification-pattern/" rel="follow noopener" target="_self" role="button"><div class="uagb-button__link">Leia o post completo</div></a></div></div>
</div></div>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">De 2021 a 2025 muita coisa aconteceu:</h2>



<p>Nesse período, parece que se intensificaram os esforços. Vamos ver alguns vídeos interessantes.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">JUL/2021 &#8211; Nick Chapsas<br />O custo oculto das exceções no .NET</h3>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="The hidden cost of Exceptions in .NET" width="1080" height="608" src="https://www.youtube.com/embed/2f2elFRmeLE?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">JUN/2022 &#8211; Nick Chapsas<br />Não lance exceções em C#. Faça isso em vez disso</h3>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Don&#039;t throw exceptions in C#. Do this instead" width="1080" height="608" src="https://www.youtube.com/embed/a1ye9eGTB98?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">ABR/2024 &#8211; Nick Chapsas<br />.NET 9 corrigiu exceções, mas ainda não as use</h3>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title=".NET 9 Fixed Exceptions but Still Don’t Use Them" width="1080" height="608" src="https://www.youtube.com/embed/wB6jjAjpjIU?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">JUL/2024 &#8211; Milan Jovanović<br />Exceções são extremamente caras… Faça isso em vez disso</h3>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Exceptions Are Extremely Expensive… Do This Instead" width="1080" height="608" src="https://www.youtube.com/embed/E3dU9Y1CsnI?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">JUL/2024 &#8211; Gui Ferreira <br />Adeus Exceções! Olá Padrão de Resultado!</h3>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Goodbye Exceptions! Hello Result Pattern!" width="1080" height="608" src="https://www.youtube.com/embed/C_u1WottRA0?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">SET/2024 &#8211; Dan Patrascu<br />Quando lançar exceções?</h3>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="When To Throw Exceptions?" width="1080" height="608" src="https://www.youtube.com/embed/aY7B_lp7eWs?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">DEZ/2024 &#8211; Filip Ekberg<br />Pare de usar exceções em C#! Aqui está o porquê e o que fazer em vez disso</h3>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="Stop Using Exceptions in C#! Here’s Why and What to Do Instead" width="1080" height="608" src="https://www.youtube.com/embed/w7RML7nBh04?feature=oembed"  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Fev/2025 &#8211; No LinkedIn</h2>



<p>No início de Fev/2025, no LinkedIn, abordei a proliferação de try-catchs desnecessários em um projeto que tive acesso, em um processo seletivo.</p>



<figure class="wp-block-image aligncenter size-full"><a href="https://www.linkedin.com/posts/luizcarlosfaria_n%C3%A3o-h%C3%A1-um-motivo-racional-para-entupir-o-activity-7295779695684456450-T1YN?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAAAJY06oBgMQGZ39ZmLUIAc8XuvxcQ4_TKFk" target="_blank" rel=" noreferrer noopener"><img data-dominant-color="f3f3f3" data-has-transparency="true" style="--dominant-color: #f3f3f3;" loading="lazy" decoding="async" width="665" height="781" src="https://gago.io/wp-content/uploads/2025/02/image-3.png" alt="" class="wp-image-20516 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/02/image-3.png 665w, https://gago.io/wp-content/uploads/2025/02/image-3-480x564.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 665px, 100vw" /></a><figcaption class="wp-element-caption">Clique na imagem para ver o post original no LinkedIn</figcaption></figure>



<p>Durante o debate promovido nos comentários, um comentário me chamou a atenção. Embora ele tenha dito que não era a meu respeito. </p>



<p class="has-text-align-center has-vivid-red-color has-black-background-color has-text-color has-background has-link-color has-medium-font-size wp-elements-4252b940da7aecbaf0b4f15a135b55e4">Como resultado, passei o final de semana rodando benchmarks <br />e o resultado foi surpreendente.</p>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="has-medium-font-size">Mas não poderia pecar na mesma negligência da qual luto contra. Não poderia incorrer no mesmo erro. Portanto, é preciso contextualizar antes de publicar o resultado desses testes, exatamente porque é tênue a linha que separa o desfazer de uma falácia e a criação de outra em seu lugar.</p>



<p>Quero cuidar da precisão dessas informações para evitar o que considero um dos maiores problemas do nosso mercado: <strong>conteúdos rasos, com contextos rasos, sem fundamentos, pautados em achismos e perspectivas não fundamentadas.</strong></p>



<h2 class="wp-block-heading">Não acaba aqui, nossos próximos passos</h2>



<p>Este artigo é apenas o ponto de partida de uma série de investigações. As próximas publicações abordarão um estudo empírico detalhado, com benchmarks rigorosos e simulações práticas. <strong><span style="color: #ff0000;" class="stk-highlight">O objetivo será quantificar com precisão se exceptions representam um gargalo de desempenho significativo ou se sua suposta ineficiência é um mito amplificado por análises superficiais.</span></strong></p>



<p>Além disso, exploraremos:</p>



<ul class="wp-block-list">
<li>Casos de uso onde exceptions podem ser substituídas por abordagens alternativas sem perda de robustez;</li>



<li>A relação entre exceptions e arquitetura de software, analisando impactos além da performance bruta;</li>



<li>Como boas práticas podem mitigar possíveis desvantagens no uso de exceptions.</li>
</ul>



<p>A complexidade desse tema exige uma abordagem meticulosa, baseada em evidências concretas e análises aprofundadas. Estamos apenas começando a desvendar essa questão.</p>



<p>Se há algo que ainda não sabemos, precisamos descobrir. </p>



<p>E é exatamente isso que faremos nos próximos capítulos desta série.</p>The post <a href="https://gago.io/blog/custo-real-das-exceptions-01-a-serie/">O custo real das exceptions – 01 – A série</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/custo-real-das-exceptions-01-a-serie/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Vai criar um IConfiguration? Não esqueça as variáveis de ambiente!</title>
		<link>https://gago.io/blog/iconfiguration-envvar/</link>
					<comments>https://gago.io/blog/iconfiguration-envvar/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Sat, 08 Feb 2025 20:14:02 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[.NET Core]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Configuração]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20365</guid>

					<description><![CDATA[<p>Quando construímos aplicações em .NET (especialmente .NET Core e posteriores), temos acesso a um pipeline de configuração bastante poderoso, que permite combinar diversas fontes de configuração: arquivos JSON, variáveis de ambiente, User Secrets, Azure Key Vault, entre outras. Entretanto, não é tão raro esbarrar projetos que utilizem exclusivamente arquivos appsettings.json (e suas variações, como appsettings.Development.json, [&#8230;]</p>
The post <a href="https://gago.io/blog/iconfiguration-envvar/">Vai criar um IConfiguration? Não esqueça as variáveis de ambiente!</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Quando construímos aplicações em .NET (especialmente .NET Core e posteriores), temos acesso a um pipeline de configuração bastante poderoso, que permite combinar diversas fontes de configuração: <strong>arquivos JSON</strong>, <strong>variáveis de ambiente</strong>, <strong>User Secrets</strong>, <strong>Azure Key Vault</strong>, entre outras.</p>



<p>Entretanto, não é tão raro esbarrar projetos que utilizem exclusivamente arquivos <em>appsettings.json</em> (e suas variações, como <em>appsettings.Development.json</em>, <em>appsettings.Production.json</em>, etc.) e inadvertidamente <strong><span style="color: #ff0000;" class="stk-highlight">não só se ignorem o uso de variáveis de ambiente, mas também impossibilitem sua utilização</span></strong>. </p>



<p>O inferno tem um lugar especial para quem faz isso intencionalmente.</p>



<span id="more-20365"></span>



<p>Você talvez já tenha visto projetos manipulando o <strong>IConfigurationBuilder </strong>para criar instâncias de <strong><code>IConfiguration</code></strong>.</p>



<p>Por exemplo, ao pesquisar por &#8220;IConfigurationBuilder IConfiguration&#8221; no google esbarramos na primeira página com uma publicação que sugere o seguinte:</p>



<figure class="wp-block-image size-large"><img data-dominant-color="1a222a" data-has-transparency="true" style="--dominant-color: #1a222a;" loading="lazy" decoding="async" width="1024" height="376" src="https://gago.io/wp-content/uploads/2025/02/image-1024x376.png" alt="" class="wp-image-20456 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/02/image-980x360.png 980w, https://gago.io/wp-content/uploads/2025/02/image-480x176.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Nesse contexto, temos uma questão.</p>



<p class="has-white-color has-vivid-cyan-blue-to-vivid-purple-gradient-background has-text-color has-background has-link-color wp-elements-7072911c2e9ae859c346bb1538e7590d">Essa abordagem remove o que esperamos que seja default em um projeto asp.net. <br />Remove o que a Microsoft entrega como ponto de partida.</p>



<h2 class="wp-block-heading">O que esperamos de um projeto .NET qualquer?</h2>



<p>Não esperamos muito, apenas o que já vem com o ASP.NET, só esperamos que ninguém sacaneie o projeto removendo as capacidades básicas de configuração.</p>



<p>Pois é isso que a Microsoft entrega no <a href="https://github.com/dotnet/runtime/blob/v9.0.0/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L236#L263" target="_blank" rel="noopener" title="código do ASP .NET">código do Runtime .NET</a> :</p>



<figure class="wp-block-image size-large"><img data-dominant-color="181f24" data-has-transparency="true" style="--dominant-color: #181f24;" loading="lazy" decoding="async" width="1024" height="743" src="https://gago.io/wp-content/uploads/2025/02/image-2-1024x743.png" alt="" class="wp-image-20458 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/02/image-2-980x711.png 980w, https://gago.io/wp-content/uploads/2025/02/image-2-480x348.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Isso quer dizer que, qualquer um que estiver diante de um projeto .NET tem a expectativa de que:</p>



<ul class="wp-block-list">
<li>Possa se usar o arquivo <strong>appsettings.json</strong></li>



<li>Que o arquivo $&#8221;appsettings.{env.EnvironmentName}.json&#8221; que usa EnvironmentName como parte do nome do arquivo.</li>



<li>Que em desenvolvimento, seja possível usar UserSecrets</li>



<li>Que possa usar Variáveis de Ambiente.</li>



<li>Que possa usar o args, enviado para o processo.</li>
</ul>



<p>Essa exata sequência é esperada. Nessa ordem, porque a próxima fonte de configuração sobrescreve as fontes de configuração anteriores.</p>



<h2 class="wp-block-heading">Mas o que você quer dizer com sobrescrever?</h2>



<p class="has-medium-font-size">As fontes se somam e, assim durante o carregamento o que for comum à duas fontes, é sobrescrito pela última. Last Win.</p>



<p>Assim, se no appsettings.json temos um json </p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{ 
    "ConnectionStrings": { 
         "xpto": "exemplo1" 
    } 
} </pre>



<p>basta usarmos a variável de ambiente <code>ConnectionStrings__xpto</code> para sobrescrever o valor com algo que a aplicação reconhecerá.</p>



<p>A variável de ambiente não sobrescreve o arquivo, <strong>as fontes se somam em memória</strong>, essa a grande diferença desse mecanismo em relação ao que tínhamos no web.config.</p>



<h2 class="wp-block-heading">Por que usar variáveis de ambiente?</h2>



<ol class="wp-block-list">
<li><strong>Segurança</strong>: Em muitos cenários, preferimos não colocar senhas e credenciais em arquivos de configuração (que acabam versionados no controle de versão). É mais seguro passar estes dados via variáveis de ambiente ou via serviços de “secret management”.</li>



<li><strong>Flexibilidade</strong>: Quando rodamos a aplicação em diferentes ambientes (desenvolvimento local, homologação, produção), nem sempre é prático ter um <em>appsettings.json</em> para cada pequena variação. As variáveis de ambiente permitem mudar configurações sem precisar mexer no arquivo e sem precisar fazer <em>deploy</em> de novos arquivos.</li>



<li><strong>Contêineres</strong>: Em orquestradores como Docker, Kubernetes, etc., definir variáveis de ambiente nos contêineres é um padrão, simplificando a gestão de configurações externas.</li>



<li><strong>Cloud</strong>: Muitos cloud providers possuem mecanismos de gerenciamento de de configuração que possuem mecanismos para fazer bind da configuração com variáveis de ambiente, assim é possível determinar rollouts que atualizem essas variáveis reiniciando todas as aplicações dependentes dela.</li>
</ol>



<h2 class="wp-block-heading">Variáveis de ambiente são best in class?</h2>



<p>Negativo: Variáveis de ambiente são melhores do que arquivos de configuração, mas serviços como Vault, são ainda melhores e mais seguros, mas como tudo em tecnologia, <strong>não são para todos nem para todos os projetos</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Conclusão</h2>



<p>Apesar do template padrão do .NET Core já fornecer uma configuração com suporte a variáveis de ambiente, é <strong>comum</strong> ver projetos que, por desconhecimento ou tentativa de simplificação, removem esse suporte ou simplesmente não o utilizam em desenvolvimento. Isso leva a grandes dores de cabeça na hora de colocar a aplicação em produção ou em contêineres, pois então surgem necessidades de reescrever partes do projeto para considerar variáveis de ambiente.</p>



<p><strong>O melhor caminho</strong> é desde o início <strong>assumir</strong> que sua aplicação será configurada por múltiplas fontes (appsettings, variáveis de ambiente, secrets etc.), mantendo o pipeline de configuração <strong>completo</strong> e aproveitando as funcionalidades já existentes no .NET Core. Desse modo, você não ficará “preso” a arquivos JSON e terá maior flexibilidade e segurança para lidar com credenciais e demais configurações em produção.</p>The post <a href="https://gago.io/blog/iconfiguration-envvar/">Vai criar um IConfiguration? Não esqueça as variáveis de ambiente!</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/iconfiguration-envvar/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Sobre MediatR</title>
		<link>https://gago.io/blog/mediatr-intocavel/</link>
					<comments>https://gago.io/blog/mediatr-intocavel/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Sat, 08 Feb 2025 14:52:01 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[MediatR]]></category>
		<category><![CDATA[Monolith]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20494</guid>

					<description><![CDATA[<p>Nos últimos anos, o MediatR tornou-se amplamente utilizado na comunidade .NET. Muitos projetos são construídos a partir de templates que o incorporam, resultando em uma adoção generalizada. No entanto, poucas pessoas questionam a necessidade real de sua utilização. Em algumas ocasiões, fui questionado sobre os motivos para não usar MediatR. Para muitos desenvolvedores, sua presença [&#8230;]</p>
The post <a href="https://gago.io/blog/mediatr-intocavel/">Sobre MediatR</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p class="has-text-align-left">Nos últimos anos, o MediatR tornou-se amplamente utilizado na comunidade .NET. Muitos projetos são construídos a partir de templates que o incorporam, resultando em uma adoção generalizada. No entanto, poucas pessoas questionam a necessidade real de sua utilização.</p>



<p>Em algumas ocasiões, fui questionado sobre os motivos para <strong>não usar MediatR</strong>. Para muitos desenvolvedores, sua presença em um projeto é considerada um padrão inevitável, algo que não precisa de justificativa ou debate.</p>



<p>Neste artigo, quero explorar essa ferramenta sob uma ótica mais crítica e avaliar seu impacto real na arquitetura de software.</p>



<span id="more-20494"></span>



<h2 class="wp-block-heading">Por que usamos o MediatR?</h2>



<p>Quando participo da definição de uma arquitetura, sempre faço a pergunta: <strong>Qual problema estamos tentando resolver com MediatR?</strong></p>



<p>Se as justificativas se resumirem a respostas como:</p>



<ul class="wp-block-list">
<li>&#8220;Sempre fizemos assim&#8221; / &#8220;É assim que se faz&#8221;</li>



<li>“Foi assim que aprendi“ / </li>



<li>“É assim que fulano faz“ / “É assim que fazíamos na empresa XXX” (em geral, essa resposta usada como argumento de autoridade)</li>



<li>&#8220;É um padrão amplamente adotado.&#8221;</li>



<li>&#8220;Os templates mais comuns já incluem MediatR.&#8221;</li>
</ul>



<p>Então, é importante ficar atento, pois há fortes indícios de que o uso da biblioteca pode estar sendo motivado mais por convenção do que por necessidade técnica.</p>



<p>Isso <span style="color: #ff0000;" class="stk-highlight"><strong>não significa que o MediatR seja uma escolha ruim por si só</strong></span>. Meu objetivo não é criticá-lo, mas sim incentivar uma reflexão sobre seus reais benefícios e desafios. Um dos pontos que mais me preocupa é a falsa sensação de desacoplamento que sua adoção pode criar.</p>



<h2 class="wp-block-heading">MediatR e o Desacoplamento</h2>



<p>Uma das principais razões para utilizar o MediatR é a ideia de reduzir o acoplamento entre diferentes partes do código. No entanto, na maioria dos casos, ele é usado para implementar um modelo <strong>request/response</strong>, criando apenas uma camada de indireção entre quem faz a chamada e quem a atende.</p>



<p>O problema é que essa dependência <strong>não desaparece</strong>, apenas fica oculta. O chamador ainda depende diretamente do manipulador da requisição, mas agora através de um intermediário, o que pode adicionar complexidade sem um benefício claro.</p>



<p>Essa abordagem pode dificultar a leitura e manutenção do código. Com o MediatR, um simples fluxo de execução pode se tornar mais trabalhoso de entender, exigindo que os desenvolvedores naveguem entre múltiplas classes para compreender a relação entre os componentes.</p>



<p>Isso pode ser insignificante em aplicações pequenas, mas à medida que a complexidade cresce, essa abstração pode se tornar um obstáculo ao invés de um benefício.</p>



<h2 class="wp-block-heading">Quando o MediatR é realmente útil?</h2>



<p>O MediatR brilha quando utilizado em <strong>propagação de notificações</strong> ou <strong>publicação de eventos</strong>. Em um cenário <strong>1 para N</strong>, onde um componente emite uma mensagem e múltiplos receptores podem agir sobre ela sem depender de respostas diretas, ele pode ser bastante útil.</p>



<p>Além disso, o MediatR pode ser uma solução eficiente para evitar dependências cíclicas dentro do domínio da aplicação.</p>



<p>Por outro lado, <strong><span style="color: #ff0000;" class="stk-highlight">se</span></strong> o fluxo continua sendo estritamente sequencial e cada chamada depende da resposta da outra, o benefício do MediatR se torna questionável. Nesses casos, um serviço injetado diretamente pode ser uma solução mais simples, eficiente e direta.</p>



<h2 class="wp-block-heading">Impacto na Performance</h2>



<p>Outro aspecto que raramente é considerado é o impacto na performance. Embora em aplicações pequenas isso não seja um grande problema, em sistemas maiores a indireção extra pode gerar latências desnecessárias.</p>



<p>Cada requisição passa por um pipeline, o que pode aumentar a sobrecarga, especialmente se houver behaviors e middlewares adicionais configurados. Isso pode resultar em maior consumo de memória e pior desempenho em comparação com uma abordagem baseada em injeção de dependência direta.</p>



<p>Portanto, antes de adotar o MediatR, é importante avaliar se essa complexidade adicional realmente compensa em termos de manutenção e performance.</p>



<h2 class="wp-block-heading">Beleza e Simplicidade</h2>



<p>O uso do MediatR adiciona uma camada de indireção, separando a solicitação (request) de sua execução. Essa abordagem pode parecer mais verbosa à primeira vista, mas introduz um belo padrão arquitetural.</p>



<h3 class="wp-block-heading">Comparação: Implementação Direta vs MediatR em Request/Response</h3>



<p>Ao comparar uma implementação direta com uma implementação usando MediatR no padrão request/response, podemos perceber nuances que vão além da simples organização do código. Vamos ilustrar isso com um exemplo prático.</p>



<h3 class="wp-block-heading">Implementação Direta:</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class PedidoService
{
    private readonly IPagamentoService _pagamentoService;

    public PedidoService(IPagamentoService pagamentoService)
    {
        _pagamentoService = pagamentoService;
    }

    public bool ProcessarPedido(Pedido pedido)
    {
        return _pagamentoService.RealizarPagamento(pedido);
    }
}</pre>



<p>Neste caso, temos um código simples e direto. A dependência é explicitamente declarada e facilmente rastreável, tornando o código mais legível e de fácil depuração.</p>



<h3 class="wp-block-heading">Por que é belo?</h3>



<p>A beleza da implementação direta está na sua simplicidade, clareza e ausência de dúvidas. O fluxo de execução é evidente, o que facilita o entendimento e a manutenção do código. Quando um novo desenvolvedor precisa compreender a lógica, ele não precisa navegar entre múltiplas camadas ou abstrações, tornando o processo mais eficiente.</p>



<h3 class="wp-block-heading">Implementação com MediatR:</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class ProcessarPedidoCommand : IRequest&lt;bool>
{
    public Pedido Pedido { get; }
    public ProcessarPedidoCommand(Pedido pedido) => Pedido = pedido;
}

public class ProcessarPedidoHandler : IRequestHandler&lt;ProcessarPedidoCommand, bool>
{
    private readonly IPagamentoService _pagamentoService;
    
    public ProcessarPedidoHandler(IPagamentoService pagamentoService)
    {
        _pagamentoService = pagamentoService;
    }

    public Task&lt;bool> Handle(ProcessarPedidoCommand request, CancellationToken cancellationToken)
    {
        return Task.FromResult(_pagamentoService.RealizarPagamento(request.Pedido));
    }
}</pre>



<p>A beleza do MediatR reside na <strong>organização e padronização da comunicação entre componentes da aplicação</strong>. </p>



<p>É importante ressaltar que, caso seja necessário adicionar novos comportamentos, como logs, validações ou métricas, podemos fazê-lo por meio de behaviors, conseguimos fazer isso sem modificar diretamente os handlers.</p>



<p>O uso do MediatR adiciona uma camada de indireção. Essa abordagem pode parecer mais verbosa à primeira vista. Nesse contexto, ele reduz o acoplamento direto entre serviços, transformando acoplamento direto em indireto, quase oculto. E aqui mora minha ressalva.</p>



<p>MediatR continua sendo uma boa escolha, mas é preciso ter clareza sobre seus objetivos de uso no projeto.</p>



<h3 class="wp-block-heading">Conclusão</h3>



<p>O MediatR não deve ser adotado automaticamente, apenas porque é um padrão popular. Ele tem muitos méritos, mas deve ser aplicado de forma criteriosa, como praticamente tudo em nossos projetos. Antes de implementá-lo, vale a pena considerar:</p>



<ul class="wp-block-list">
<li>Qual problema ele realmente resolve no contexto do projeto?</li>



<li>Ele está promovendo um desacoplamento genuíno ou apenas escondendo dependências?</li>



<li>O código ficaria mais claro e simples sem essa camada adicional?</li>



<li>O impacto na performance justifica a adoção dessa abordagem?</li>
</ul>



<p>Ferramentas e padrões de design existem para resolver problemas, não para serem utilizados indiscriminadamente. O MediatR pode ser uma solução interessante, mas sua adoção precisa ser pensada para ser estratégica. Senão, se torna apenas mais um peso que aumenta a complexidade sem trazer benefícios.</p>



<p class="has-text-align-left">E você? Como tem utilizado o MediatR em seus projetos? Tem encontrado mais benefícios ou desafios?</p>The post <a href="https://gago.io/blog/mediatr-intocavel/">Sobre MediatR</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/mediatr-intocavel/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Há momentos em que é preciso dizer NÃO para o negócio</title>
		<link>https://gago.io/blog/alguem-precisa-dizer-nao-para-o-negocio/</link>
					<comments>https://gago.io/blog/alguem-precisa-dizer-nao-para-o-negocio/#respond</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Mon, 03 Feb 2025 19:41:14 +0000</pubDate>
				<category><![CDATA[Desenvolvimento]]></category>
		<guid isPermaLink="false">https://gago.io/?p=20432</guid>

					<description><![CDATA[<p>No cenário atual de desenvolvimento de sistemas, especialmente em ambientes de alta demanda, a realidade se impõe: nem sempre é possível atender a todas as solicitações do negócio sem alterar a base técnica. Em diversas situações, é necessário que o arquiteto exerça a maturidade de saber quando dizer “NÃO” para o negócio – mesmo diante [&#8230;]</p>
The post <a href="https://gago.io/blog/alguem-precisa-dizer-nao-para-o-negocio/">Há momentos em que é preciso dizer NÃO para o negócio</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>No cenário atual de desenvolvimento de sistemas, especialmente em ambientes de alta demanda, a realidade se impõe: nem sempre é possível atender a todas as solicitações do negócio sem alterar a base técnica. Em diversas situações, é necessário que o arquiteto exerça a maturidade de saber quando dizer “NÃO” para o negócio – mesmo diante de uma cultura fortemente inclinada ao “cliente tem sempre a razão”. </p>



<p>Hoje discutimos os desafios de equilibrar anseios contraditórios, a tensão entre requisitos funcionais e não funcionais, e como essa decisão pode ser determinante para a sustentabilidade e o desempenho dos sistemas.</p>



<span id="more-20432"></span>



<h2 class="wp-block-heading">Os anseios de negócio: contradições e desafios</h2>



<p>As empresas, por natureza, buscam crescimento, expansão e resultados imediatos. Muitas vezes, a demanda é por soluções que operem de maneira “tradicional”, sem que seja necessário repensar processos ou alterar estruturas consolidadas. Essa visão, que preza por manter o status quo, entra em conflito com a realidade técnica de que atender a volumes massivos sem promover mudanças é, simplesmente, inviável.</p>



<p>Em muitas organizações, há uma pressão intensa para que cada nova demanda seja rapidamente absorvida, mantendo a expectativa de que o sistema seja capaz de suportar volumes crescentes sem que se precise alterar o fluxo ou os mecanismos internos. Esse ideal, contudo, entra em colisão com os limites técnicos, que exigem a aplicação de estratégias de gerenciamento de carga, otimização de processos e, em alguns casos, a adoção de mecanismos que podem modificar o comportamento tradicional do sistema.</p>



<p class="has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-ceeba2bc4ead2269924115c9d1d64c9a"><em>Imagine uma empresa de seguros que deseja implementar, de forma imediata, um novo módulo para análise de riscos em tempo real. O negócio acredita que, se essa funcionalidade estiver disponível, os clientes terão uma experiência mais dinâmica e a conversão de vendas aumentará. No entanto, a equipe técnica, ao analisar os requisitos, percebe que o sistema atual <strong>não foi projetado</strong> para processar em tempo real esse volume de dados complexos, o que pode resultar em lentidão ou até falhas no processamento. Nesse caso, apesar da demanda do negócio, os arquitetos recomendam uma abordagem gradual – implementando primeiro uma versão assíncrona e monitorada – antes de migrar para o processamento em tempo real.</em></p>



<h2 class="wp-block-heading">A Cultura do “cliente tem sempre a razão” no Brasil</h2>



<p>No Brasil existe um hábito quase arraigado de dizer “sim” a todas as solicitações, independentemente da complexidade ou dos desafios envolvidos. Essa postura, muitas vezes, decorre de uma cultura de <strong>otimismo exagerado</strong> ou da <strong>necessidade de agradar</strong> o cliente. Entretanto, essa atitude pode levar a promessas que, na prática, não se concretizam de forma executável.</p>



<p>Os mecanismos de cobrança e as pressões internas contribuem para que essa cultura se perpetue, mesmo quando as condições técnicas apontam para a impossibilidade de uma implementação sem alterações significativas. Essa realidade cria um ambiente onde o diálogo entre os diferentes setores da empresa – especialmente entre os times de negócios e de tecnologia – torna-se fundamental para estabelecer expectativas realistas e construir soluções robustas.</p>



<p class="has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-0adb984ee8302abbfe64d155c2fc4bbb"><em>Em uma startup de tecnologia, a liderança frequentemente solicita novas funcionalidades sem uma análise profunda da infraestrutura atual. Por exemplo, o time de negócios decidiu adicionar uma funcionalidade de chat em tempo real para suporte ao cliente durante picos de vendas, sem considerar os impactos no servidor. Os desenvolvedores, pressionados, implementaram o recurso rapidamente. Após alguns dias de uso, o sistema começa a apresentar lentidão e instabilidades, justamente porque a arquitetura não previa um volume tão alto de conexões simultâneas. Esse cenário força a equipe técnica a interromper a funcionalidade para realizar ajustes na infraestrutura, evidenciando que um “sim” precipitado pode comprometer a qualidade do serviço. Afinal, qual a experiência dos clientes durante a instabilidade?</em></p>



<h2 class="wp-block-heading">A divergência entre requisitos funcionais e não funcionais</h2>



<p>Uma das principais fontes de tensão em times técnicos é causada pela forma como os profissionais são cobrados: </p>



<p>De um lado, o desenvolvedor é avaliado com base na entrega dos requisitos funcionais – ou seja, a capacidade de implementar as funcionalidades solicitadas. <br />De outro, o arquiteto é responsável pelos requisitos não funcionais, como desempenho, escalabilidade, segurança e confiabilidade do sistema.</p>



<p>Essa divisão gera, muitas vezes, um cenário de conflito. O time de desenvolvimento foca na criação de funcionalidades que atendam às demandas do negócio, enquanto os arquitetos precisam garantir que essas soluções possam suportar um volume crescente de acessos e transações, sem comprometer a qualidade do sistema. Dizer “NÃO” para o negócio, neste contexto, significa recusar demandas que coloquem em risco a integridade dos requisitos não funcionais, mesmo que, em um primeiro momento, pareça uma recusa ao que é pedido.</p>



<p>Conciliar essas duas perspectivas exige um diálogo constante e transparente entre todas as áreas. A maturidade para decidir quando ceder e quando resistir a novas demandas não é apenas uma questão técnica, mas também de gestão e comunicação. É preciso construir pontes que possibilitem que ambos os lados compreendam os limites e as necessidades do outro, estabelecendo prioridades que garantam a sustentabilidade do sistema a longo prazo.</p>



<p class="has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-543f2e4d0874c54fc17625f7144f9450"><em>Considere um sistema de pagamentos onde o time de desenvolvimento implementou rapidamente um novo método de checkout para aumentar as conversões – atendendo ao requisito funcional de maneira exemplar. Entretanto, os arquitetos perceberam que o novo fluxo não estava preparado para suportar o volume de transações esperado durante eventos promocionais, afetando a latência e a segurança das operações. Assim, mesmo tendo uma solução funcional, é preciso “dizer NÃO” para a implementação imediata, optando por uma revisão arquitetural que incluísse, por exemplo, balanceamento de carga e mecanismos de cache, para que o sistema se tornasse sustentável sob alta demanda.</em></p>



<h2 class="wp-block-heading">A maturidade na decisão: Quando dizer sim e quando dizer não</h2>



<p>Decidir entre dizer “sim” ou “não” para o negócio não é uma questão de teimosia ou de negativismo; é uma prática que exige visão de longo prazo e uma compreensão profunda dos limites técnicos. A maturidade, nesse contexto, é fundamental para que a equipe técnica possa fazer análises criteriosas e oferecer alternativas que, embora possam diferir da solicitação inicial, atendam aos objetivos estratégicos sem comprometer a qualidade ou a escalabilidade do sistema.</p>



<p>Essa maturidade se manifesta na <strong>capacidade de apresentar dados, projeções e análises</strong> que demonstram claramente os riscos associados à implementação de uma demanda sem os ajustes necessários. Em muitos casos, a recusa ou a <span style="color: #ff0000;" class="stk-highlight">proposta de uma solução alternativa</span> não é uma rejeição ao negócio, mas um passo estratégico para evitar problemas imediatos ou futuros – como falhas em momentos críticos, aumento de custos com retrabalho ou até mesmo a perda de credibilidade no mercado.</p>



<p class="has-vivid-cyan-blue-color has-text-color has-link-color wp-elements-65f951b4452136ff361345b3d731f1a5"><em>Em um projeto de modernização de um sistema legado, o time de negócios pressiona para que todas as funcionalidades antigas sejam migradas de uma só vez para uma nova plataforma, a fim de reduzir custos e acelerar o tempo de lançamento. O time de arquitetura, porém, identifica que uma migração em massa pode causar indisponibilidade e inconsistências críticas nos dados. Após realizar testes de carga e simulações, a equipe técnica propôem uma migração gradual, utilizando feature flags para ativar novas funcionalidades de forma controlada. Essa decisão, baseada em dados e análise, permitiu validar a estabilidade do sistema e, posteriormente, atender às demandas do negócio de maneira sustentável.</em></p>



<h2 class="wp-block-heading">Exemplo Prático: O Cenário do E-commerce</h2>



<p>Considere o caso de um e-commerce que enfrenta um volume crescente de acessos e transações. A equipe de negócios pode insistir em manter o fluxo tradicional de finalização de compra, no qual o pedido é convertido imediatamente em uma compra. Entretanto, a equipe técnica, responsável por garantir que o sistema suporte picos de acesso sem travamentos ou perdas de dados, como na Black Friday, pode identificar que esse modelo não é sustentável sem ajustes.</p>



<p>Nesse cenário, a abordagem técnica pode sugerir que, ao invés de converter o pedido em uma compra imediata, o sistema trate o clique do cliente como uma solicitação de compra – uma mudança que, embora contrarie a expectativa tradicional, permite o uso de mecanismos como processamento assíncrono e filas para gerenciar o volume sem sobrecarregar a infraestrutura. Aqui, dizer “NÃO” ao modelo tradicional não é um obstáculo, mas sim uma decisão necessária para assegurar que o sistema continue performando de forma confiável, mesmo em momentos de alta demanda.</p>



<h2 class="wp-block-heading">Conclusão</h2>



<p>Dizer “NÃO”, <span style="color: #ff0000;" class="stk-highlight">em certos momentos</span>, é uma escolha estratégica que visa a longevidade e a robustez do sistema. A tensão entre os anseios imediatos do negócio e as necessidades estruturais da tecnologia é real e exige maturidade para que as decisões sejam tomadas com base em análises técnicas sólidas e em um diálogo transparente entre todos os envolvidos.</p>



<p>No Brasil, onde a cultura do “cliente tem sempre a razão” pode facilmente obscurecer os limites técnicos, é imprescindível que as equipes de arquitetura e desenvolvimento se unam para construir pontes de entendimento. Somente assim será possível conciliar os requisitos funcionais – que atendem às demandas do negócio – com os não funcionais – que garantem a sustentabilidade do sistema –, criando soluções que não apenas respondam ao presente, mas que também estejam preparadas para a longevidade.</p>



<p>Estamos cansados de construir, quebrar, reconstruir e falhar novamente pelos mesmos erros que já cometemos no passado.</p>The post <a href="https://gago.io/blog/alguem-precisa-dizer-nao-para-o-negocio/">Há momentos em que é preciso dizer NÃO para o negócio</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/alguem-precisa-dizer-nao-para-o-negocio/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Voltando do .NET Aspire para o Docker Compose</title>
		<link>https://gago.io/blog/from-dotnet-aspire-to-docker-compose/</link>
					<comments>https://gago.io/blog/from-dotnet-aspire-to-docker-compose/#comments</comments>
		
		<dc:creator><![CDATA[Luiz Carlos Faria]]></dc:creator>
		<pubDate>Thu, 30 Jan 2025 17:50:36 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[.NET de A a Z]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Cloud Native .NET]]></category>
		<category><![CDATA[Docker de A a Z]]></category>
		<category><![CDATA[Geral]]></category>
		<category><![CDATA[The Microservices Journey]]></category>
		<category><![CDATA[.NET Aspire]]></category>
		<category><![CDATA[.NET Core]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Cloud Native]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[MicroServices]]></category>
		<guid isPermaLink="false">https://gago.io/?p=19998</guid>

					<description><![CDATA[<p>Já faz algum tempo que publiquei um post contando sobre minha experiência com o .NET Aspire. Depois de mais de 14 semanas com .NET Aspire chegou a hora de dizer “até logo”. Neste texto, vamos discutir a viabilidade de usar o .NET Aspire em projetos baseados em Docker Compose e também mostrar o processo de [&#8230;]</p>
The post <a href="https://gago.io/blog/from-dotnet-aspire-to-docker-compose/">Voltando do .NET Aspire para o Docker Compose</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></description>
										<content:encoded><![CDATA[<p>Já faz algum tempo que publiquei um post contando sobre minha experiência com o <em>.NET Aspire</em>. Depois de mais de 14 semanas com .NET Aspire chegou a hora de dizer “até logo”.</p>



<p>Neste texto, vamos discutir a viabilidade de usar o <em><strong>.NET Aspire</strong></em> em projetos baseados em <strong><em>Docker Compose</em></strong> e também mostrar o processo de migração do <em>.NET Aspire</em> de volta para o Docker Compose.</p>



<span id="more-19998"></span>



<h2 class="wp-block-heading">Recapitulando</h2>



<p>Em um post anterior, apresentei:</p>



<ul class="wp-block-list">
<li>Os componentes de <strong>produção</strong> e <strong>desenvolvimento</strong>.</li>



<li>A relevância do dashboard do <em>.NET Aspire</em>, que resolve um grande problema no fluxo de trabalho de desenvolvimento.</li>



<li>Como o Docker Compose e seu YAML quase não têm utilidade em produção (em cenários onde se usa Kubernetes).</li>
</ul>



<p>O ponto alto do dashboard do <em>.NET Aspire</em> é o <strong>baixo consumo de recursos</strong>, o que possibilita o uso de <em>Tracing</em>, <em>Log</em> e <em>métricas </em>de forma integrada na experiência do desenvolvedor a um custo baixíssimo de consumo de recursos, menos de 60 megas em alguns casos.</p>



<p>Contudo, há um contraponto: quando trabalhamos com Kubernetes, o Docker Compose se torna menos relevante em produção. Mas e durante o desenvolvimento? E se realmente precisamos dos projetos .NET rodando em containers?</p>



<h2 class="wp-block-heading">Abstraindo o uso de Containers</h2>



<p>Um problema comum no ecossistema de desenvolvimento é a busca por abstrações que transformem o desenvolvedor em “mero digitador de código”. Em muitos casos, o dev quer:</p>



<ul class="wp-block-list">
<li>Ignorar Docker</li>



<li>Ignorar Containers</li>



<li>Ignorar Kubernetes</li>



<li>Ignorar Observabilidade</li>



<li>Ignorar Mensageria e a teoria de Filas/Streaming</li>
</ul>



<p>Essa “fuga” costuma vir do desejo de “focar no negócio” e negligenciar detalhes técnicos. Embora isso, em um primeiro momento, possa deixar o cliente satisfeito, gera um cenário tende a se deteriorar no primeiro desafio.</p>



<p>Como os ciclos de vida dos desenvolvedores nas empresas são cada vez menores, muitos não ficam tempo suficiente para lidar com as consequências de decisões que tomaram. Acabam saindo da empresa antes e replicando as mesmas práticas ruins em novos lugares, como uma doença crônica silenciosa, que só é percebida quando já é tarde.</p>



<h2 class="wp-block-heading">Quanto maior o nível de desconhecimento, maior a insegurança</h2>



<p>É claro que precisamos nos adaptar aos novos tempos e trazer profissionais mais rapidamente para um nível sênior. Porém, <strong>experiência não se compra</strong> e, definitivamente, não se obtém com apenas seis meses de treinamento.</p>



<p class="has-small-font-size"><em>No Cloud Native .NET, por exemplo, já tive dificuldades ao trabalhar com desenvolvedores plenos. Eles normalmente não estão acostumados a tomar decisões arquiteturais ou a resolver problemas complexos sozinhos. Geralmente seguem soluções “enlatadas”, fornecidas pelos seniores. Essa falta de vivência real e prática pesa substancialmente.</em></p>



<p>Quando falamos de projetos baseados em containers, fazer tudo rodar em containers durante o desenvolvimento faz enorme diferença.</p>



<h2 class="wp-block-heading">Aspire atrapalha ou ajuda?</h2>



<p>TL;DR; Ajuda, mas o o orquestrador é o componente controverso.</p>



<p>A minha experiência com o .NET Aspire foi muito positiva. Trata-se de uma solução bem feita, entretanto cria um distanciamento perigoso entre o .NET e Containers.</p>



<h3 class="wp-block-heading">🔴Orquestrador</h3>



<p>O Orquestrador do .NET Aspire é embarcado na aplicação AppHost, que por sua vez exibe um Dashboard (que pode ser usado isolado).</p>



<p>Sobre o orquestrador, tenho 2 críticas:</p>



<ul class="wp-block-list">
<li>ou deveria <span style="color: #ff0000;" class="stk-highlight">substituir o Docker Compose</span> permitindo aplicações .NET rodando em Containers com debug e toda a experiência que temos com Docker Compose no Visual Studio.</li>



<li>ou <span style="color: #ff0000;" class="stk-highlight">ser substituído pelo Docker Compose</span></li>
</ul>



<p class="has-text-color has-link-color has-medium-font-size wp-elements-4daefaddb78f4b97bde9162e9bd968a9" style="color:#ff0000">O que definitivamente não quero é que minhas aplicações .NET <br />que serão containers Linux, <br />sejam desenvolvidas sem containers Linux.</p>



<h3 class="wp-block-heading">🔴Service Discovery</h3>



<p>A implementação <em>Service Discovery</em> é útil se você estiver rodando suas aplicações .NET no Windows. Com cada API rodando em uma porta diferente no <code>localhost</code>, ele se faz necessário e cumpre seu papel. </p>



<p>Mas na experiência com containers, onde cada container tem um nome, endereçável, e o mecanismo de service discovery é baseado em DNS, uma tecnologia de 1983, compatível com absolutamente qualquer coisa, essa implementação perde relevância e importância.</p>



<p>Então, no universo de containers, a implementação de Service Discovery do .NET Aspire é desnecessária.</p>



<h3 class="wp-block-heading">🟢Open Telemetry</h3>



<p>As configurações de Open Telemetry presentes em componentes e integrações do <em>.NET Aspire</em> são <strong><span style="background-color: #00d084" class="stk-highlight">indiscutivelmente úteis e muito bem feitas</span></strong>. Não tenho do que reclamar, inclusive é algo que trouxe para o Docker Compose, com o dashboard.</p>



<h3 class="wp-block-heading">🟢Dashboard</h3>



<p>Se você já subiu ou tentou subir qualquer <span style="color: #ff0000;" class="stk-highlight">stack completa de observabilidade</span>, provavelmente percebeu que o dashboard do .NET Aspire é um quebra-galho muito bom.</p>



<p>Eu vi na comunidade .NET um certo número de pessoas elogiando o Dashboard do .NET Aspire, como se fosse algo maravilhoso. Calma, né?! Pera lá. Seja Kibana ou Grafana, ou similares, temos opções mais profissionais, mais completas, mais flexíveis, desenhadas para cluster e produção, stateful, e mais customizáveis.</p>



<p>Dizer que o Dashboard do Aspire &#8220;é o que faltava&#8221; ou é o suficiente, é ridículo, uma demonstração absoluta de desconhecimento.</p>



<p>A grande carta na manga do Dashboard do .NET Aspire é:</p>



<ol class="wp-block-list">
<li>De um lado, ser um único componente que incorpora 4 responsabilidades (que demandariam 4 componentes):
<ul class="wp-block-list">
<li>OpenTelemetry Collector</li>



<li>Log</li>



<li>Tracing</li>



<li>Metrics</li>
</ul>
</li>



<li>E seu <span style="color: #ff0000;" class="stk-highlight">verdadeiro poder</span> está no <strong><span style="color: #ff0000;" class="stk-highlight">baixíssimo consumo de recursos</span></strong>.</li>
</ol>



<p> <strong>O baixo consumo de recursos é o destaque</strong>, vezes mais relevante do que a quantidade de componentes que abstrai e vezes mais relevante que a simplicidade do dashboard.</p>



<figure class="wp-block-image size-large"><img data-dominant-color="f1f2f8" data-has-transparency="true" style="--dominant-color: #f1f2f8;" loading="lazy" decoding="async" width="1024" height="642" src="https://gago.io/wp-content/uploads/2025/01/image-5-1024x642.png" alt="" class="wp-image-20368 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/01/image-5-980x615.png 980w, https://gago.io/wp-content/uploads/2025/01/image-5-480x301.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<h3 class="wp-block-heading">🟢Containers auxiliares</h3>



<p>O .NET Aspire também oferece praticidade na hora de adicionar containers auxiliares que administram serviços como Postgres, Redis etc. e configurá-los de forma integrada. Isso simplifica a adoção de ferramentas como <em>PgWeb</em>, <em>PgAdmin</em> ou <em>Redis Commander</em>, por exemplo.</p>



<p>Embora também sejam ferramentas que rodam sob containers, o distanciamento aqui não é muito relevante na medida que essas ferramentas que usamos para suportar o ambiente local, em geral, não serão as mesmas de produção. Dificilmente usaremos <em>PgWeb</em>, <em>PgAdmin</em> ou <em>Redis Commander</em>, em produção, portanto o distanciamento dessas configurações e containers é irrelevante.</p>



<h3 class="wp-block-heading">🟢 Configurações de resiliência</h3>



<p>Outro grande avanço são as configurações de resiliência e observabilidade automáticas entregues pelos componentes/integrações. Não poderia deixar de mencionar o acerto e quão bem-vindo foi.</p>



<h2 class="wp-block-heading">O peso do distanciamento</h2>



<p>É certo que não estamos aqui para aprender tecnologia, estamos aqui para <span style="color: #ff0000;" class="stk-highlight">entregar negócio</span> <span style="color: #0c6041;" class="stk-highlight">com tecnologia</span>. Aprender alguma tecnologia pode ser uma necessidade para entregar mais negócio, mas não é um objetivo em si. </p>



<p>Só não posso fechar os olhos sobre quão disruptivo é o universo Linux, para usuários nativos do Windows. O universo Cloud Native é desafiador principalmente para um desenvolvedor .NET , <strong><span style="color: #ff0000;" class="stk-highlight">perder a possibildiade de <em>debugar</em> aplicações .NET containerizadas no linux é um problema. </span></strong></p>



<h3 class="wp-block-heading">Para quem é um problema?</h3>



<p>Para mim, que sequer aprovo candidatos que não tenham conhecimento minimamente aprofundado em containers, quanto para alguns líderes que conversaram comigo nos últimos anos* e estão nessa jornada investindo em conhecimento para que seus times deixem de ser perdidos.</p>



<p class="has-small-font-size">* Essas conversas nasceram quando <br />o docker-shim foi descontinuado ( Manchete: <strong>Kubernetes abandona docker</strong>) <br />e quando a docker criu sua subscription (Manchete: <strong>Docker agora é pago</strong>).</p>



<figure class="wp-block-image aligncenter size-full"><a href="https://github.com/dotnet/aspire/discussions/6814" target="_blank" rel=" noreferrer noopener"><img data-dominant-color="eeeeef" data-has-transparency="true" style="--dominant-color: #eeeeef;" loading="lazy" decoding="async" width="653" height="135" src="https://gago.io/wp-content/uploads/2025/01/image-7.png" alt="" class="wp-image-20372 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/01/image-7.png 653w, https://gago.io/wp-content/uploads/2025/01/image-7-480x99.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 653px, 100vw" /></a></figure>



<figure class="wp-block-image aligncenter size-full"><a href="https://github.com/dotnet/aspire/discussions/6814" target="_blank" rel=" noreferrer noopener"><img data-dominant-color="f2f5f5" data-has-transparency="true" style="--dominant-color: #f2f5f5;" loading="lazy" decoding="async" width="694" height="412" src="https://gago.io/wp-content/uploads/2025/01/image-8.png" alt="" class="wp-image-20373 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/01/image-8.png 694w, https://gago.io/wp-content/uploads/2025/01/image-8-480x285.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 694px, 100vw" /></a></figure>



<p class="has-medium-font-size">A lógica é simples: Músculo que não é usado atrofia!</p>



<p>Então, quando me distancio e vejo meu time (ou meus alunos) se distanciarem de containers, quase que fugindo do assunto, penso: <strong>Vamos nos enfraquecer essa &#8220;musculatura&#8221;! </strong></p>



<p>E de fato a experiência de portar as configs do .NET Aspire para o Docker Compose me mostraram isso.</p>



<p>Container é algo disruptivo o suficiente para que eu possa julgar que <strong><span style="color: #ff0000;" class="stk-highlight">não faz sentido nos distanciarmos, não é o que desejo para mim e para uma parcela talvez até pequena dos Arquitetos, Líderes Técnicos e CTO&#8217;s quem converso.</span></strong></p>



<p>Porque no final do dia com .NET Aspire,<span style="color: #ff0000;" class="stk-highlight"> não paramos de usar containers, só paramos de tocar neles no ambiente de desenvolvimento</span>, com o Orquestrador do .NET Aspire colocamos uma camada de simplificação, entretanto os containers vão continuar dando problema em produção e <span style="color: #ff0000;" class="stk-highlight">agora cada vez mais porque o dev, agora com .NET ASPIRE, tem a chance de nunca &#8220;rodar&#8221; sua aplicação .NET em Containers Linux, diferente da experiência de quem usa Docker Compose. </span></p>



<p>A experiência entregue pelo .NET Aspire é útil, e talvez até vital, para uma galera mais jovem, atendendo uma demanda por devs cloud native produtivos a qualquer custo, onde Linux e Containers criam barreiras principalmente para haters do Linux.</p>



<p>Mas para aqueles que sofreram o suficiente com times perdidos, perder a possibilidade de rodar nossas aplicações .NET conteinerizadas no Linux significa aceitar a perda de autonomia, a perda da capacidade de execução, significa amarrar as nossas mãos.</p>



<p>Por outro lado, se quiséssemos alguma customização em um yaml, conseguiríamos obter referência de toda a comunidade Tech que usa docker no mundo. Com .NET Aspire, as customizações estão restritas aos projetos .NET com C#, ou seja, uma fração do mercado. O docker-compose.yaml, que poderia ser analisado no ambiente de desenvolvimento por qualquer time com o mínimo conhecimento de containers, dá lugar a um código C# que exige conhecimento muito mais específico.</p>



<p><strong>Nos distanciamos das implantações.</strong></p>



<p>Nos distanciamos drasticamente do Docker Compose e perdemos milhares de YAML&#8217;s produzidos por todas as comunidades, por todos os vendors e criadores de soluções conteinerizadas.</p>



<figure class="wp-block-image size-full"><a href="https://github.com/dotnet/aspire/discussions/4375" target="_blank" rel=" noreferrer noopener"><img data-dominant-color="fafafa" data-has-transparency="true" style="--dominant-color: #fafafa;" loading="lazy" decoding="async" width="916" height="185" src="https://gago.io/wp-content/uploads/2025/01/image-6.png" alt="" class="wp-image-20371 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/01/image-6.png 916w, https://gago.io/wp-content/uploads/2025/01/image-6-480x97.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) 916px, 100vw" /></a></figure>



<h2 class="wp-block-heading">A lógica de deployment</h2>



<p>O .NET Aspire tem no orquestrador todas as informações para que ambientes produtivos sejam criados a partir dele. Na hora de implantar, os utilitários do .NET Aspire geram deployments do Kubernetes, e serviços gerenciados dos principais cloud providers.</p>



<figure class="wp-block-image aligncenter size-large is-resized"><img data-dominant-color="575b5b" data-has-transparency="true" loading="lazy" decoding="async" width="1024" height="574" src="https://gago.io/wp-content/uploads/2025/01/image-9-1024x574.png" alt="" class="wp-image-20374 has-transparency" style="--dominant-color: #575b5b; width:400px" srcset="https://gago.io/wp-content/uploads/2025/01/image-9-980x549.png 980w, https://gago.io/wp-content/uploads/2025/01/image-9-480x269.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Isso faz com que eu me sinta quase como um gângster, forçando toda aplicação .NET Aspire a usar ou Kubernetes ou Serviços gerenciados de Cloud como se fossem as únicas opções, ignorando uma parcela significativa que continuará usando Swarm, Docker Compose on premise em servidores físicos ou virtuais, ou em VPS&#8217;s como uma parcela significativa dos MicroSaaS embrionários.</p>



<h2 class="wp-block-heading">Quais desdobramentos possíveis?</h2>



<p>Futurologia nunca foi meu departamento, mas não é muito difícil perceber que esse movimento distanciaria .NET do universo dos containers, ou pelo menos abraçaria aqueles que nunca iriam por conta da complexidade.</p>



<p>Então, são 2 pontos de vista antagônicos.</p>



<p>Um resultado previsível de uma adoção massiva do orquestrador do .NET Aspire é:</p>



<ul class="wp-block-list">
<li> Desaceleração na adoção de <strong>Docker</strong>, <strong>Docker Compose</strong>, <strong>Docker Swarm</strong>, </li>



<li>Forçar a galera de On Premise e VPS para adotarem serviços de cloud gerenciados (com ou sem kubernetes).</li>



<li>Retorno ao desenvolvimento de aplicações .NET rodando em Win32NT no IIS.</li>
</ul>



<figure class="wp-block-image aligncenter size-large is-resized"><img data-dominant-color="796c56" data-has-transparency="true" loading="lazy" decoding="async" width="1024" height="574" src="https://gago.io/wp-content/uploads/2025/01/image-10-1024x574.png" alt="" class="wp-image-20375 has-transparency" style="--dominant-color: #796c56; width:400px" srcset="https://gago.io/wp-content/uploads/2025/01/image-10-980x549.png 980w, https://gago.io/wp-content/uploads/2025/01/image-10-480x269.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></figure>



<p>Claro que não é um bicho de 7 cabeças criar um provider para .NET Aspire para gerar Yaml de Docker Compose e afins, mas também não vejo sentido.</p>



<p>Mas o fato de não rodar .NET em containers, com a mesma experiência do docker compose, é um retrocesso indigesto.</p>



<p>Me recordo de ter torcido o nariz na época to Project Tye, agora torço o nariz para algumas decisões deliberadas a respeito de voltar com o workload inteiro para o Windows.</p>



<p>A discussão <a href="https://github.com/dotnet/aspire/discussions/6814" target="_blank" rel="noopener" title="6814 ">6814 </a>dá a pista de que pode ser que seja possível em um futuro qualquer, entretanto, enquanto não chega, vou usando o máximo do .NET Aspire no Docker Compose, já temos avanços incríveis com:</p>



<ul class="wp-block-list">
<li>Service Defaults
<ul class="wp-block-list">
<li>Configurações de Telemetria.</li>



<li>Configurações de Resiliência.</li>



<li>Configurações de Service Discovery.</li>
</ul>
</li>



<li>Dashboard
<ul class="wp-block-list">
<li>Se comportando como OpenTelemetry Collector</li>



<li>Servindo Métricas</li>



<li>Servindo Logs</li>



<li>Servindo Tracing</li>
</ul>
</li>
</ul>



<p>Já é um imenso avanço.</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-default"/>



<p class="has-text-align-center has-white-color has-vivid-red-background-color has-text-color has-background has-link-color has-medium-font-size wp-elements-d308baf667d038ff6868223ba893650e">Você pode me fazer um favor?  É grátis!</p>



<p class="has-text-align-center has-white-color has-vivid-red-background-color has-text-color has-background has-link-color has-large-font-size wp-elements-c8a4a1cbdccb103dbedf58aa7fa5e567">Vote na <a href="https://github.com/dotnet/aspire/discussions/6814" target="_blank" rel="noopener" title="issue #6814 do github">issue #6814 do github</a></p>



<p class="has-text-align-center has-white-color has-vivid-red-background-color has-text-color has-background has-link-color has-medium-font-size wp-elements-b54536160acf33afa931a9bf0475f0f4">Essa issue conduz o pedido para que .NET ASPIRE suporte Containers Linux com a experiência de debug. <br /><br />Essa atividade é fundamental para uma plena experiência cloud native.</p>



<p class="has-text-align-center has-white-color has-vivid-red-background-color has-text-color has-background has-link-color has-large-font-size wp-elements-2d301c6d0e9104e6f860f3ef50ff5322">Nunca te pedi nada!!</p>



<div style="height:52px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large"><a href="https://github.com/dotnet/aspire/discussions/6814" target="_blank" rel=" noreferrer noopener"><img data-dominant-color="f3f6f4" data-has-transparency="true" style="--dominant-color: #f3f6f4;" loading="lazy" decoding="async" width="1024" height="650" src="https://gago.io/wp-content/uploads/2025/01/image-11-1024x650.png" alt="" class="wp-image-20416 has-transparency" srcset="https://gago.io/wp-content/uploads/2025/01/image-11-980x622.png 980w, https://gago.io/wp-content/uploads/2025/01/image-11-480x305.png 480w" sizes="(min-width: 0px) and (max-width: 480px) 480px, (min-width: 481px) and (max-width: 980px) 980px, (min-width: 981px) 1024px, 100vw" /></a></figure>The post <a href="https://gago.io/blog/from-dotnet-aspire-to-docker-compose/">Voltando do .NET Aspire para o Docker Compose</a> first appeared on <a href="https://gago.io">gaGO.io</a>.]]></content:encoded>
					
					<wfw:commentRss>https://gago.io/blog/from-dotnet-aspire-to-docker-compose/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
