Blog

  • Manipulando o tempo com o Day.js

    Manipulando o tempo com o Day.js

    Manipular datas e horários no JavaScript — e em linguagens de programação em geral — pode ser uma tarefa árdua. Existem inúmeras variáveis que precisamos levar em consideração ao lidar com esse tipo de informação:

    • formato;
    • local;
    • fuso horário;
    • dias da semana;
    • anos bissextos;
    • meses com 30 ou 31 dias;
    • etc.

    O que parece trivial pode se tornar uma tarefa que demanda muitas horas (e pode dar uma dor de cabeça enorme!).

    No JavaScript já temos algumas APIs para lidar com datas, mas nenhuma delas muito intuitiva. Foi aí que apareceu o Moment.js, uma pequena biblioteca de código aberto que nos permite parsear, validar, manipular e exibir datas e horários pro usuário através do JavaScript. Sua proposta foi tão bem aceita que ela se tornou referência neste aspecto.

    Porém, como tudo que é bom, ele ganhou alguns concorrentes bem legais e neste artigo discutiremos especificamente sobre um deles: o Day.js.

    Conhecendo o Day.js

    Como a própria documentação do projeto já diz:

    O Day.js é uma pequena biblioteca JavaScript (é realmente pequena, tem somente 2kb!) que nos permite parsear, manipular, validar e mostrar datas e horários em navegadores modernos.

    Além disso, ele é altamente compatível com o a API do Moment.js, de modo que o próprio site também nos diz: “Se você já usou o Moment.js, então você já sabe como usar o Day.js”.

    O Day.js é um projeto de código aberto e está publicado no GitHub sob licença MIT, e se você achava que o projeto era pequeno por ser uma alternativa ao Moment.js, saiba que está muito enganado: ele já possui mais de 16 mil estrelas no repositório.

    Ficou curioso? Vamos dar uma olhada em sua API.

    Brincando com o tempo

    Como já citado anteriormente, a biblioteca é muito semelhante ao Moment.js e nos oferece algumas funcionalidades interessantes, como o fato de ela ter uma API Chainable (encadeáveis).

    Isso significa que conseguimos encadear os métodos (invocá-los um após o outro diretamente) para formatar as informações da maneira que precisamos dentro do contexto da nossa aplicação.

    O primeiro passo para utilizar a biblioteca é instalar ela dentro do nosso projeto. Para isso, podemos utilizar o bom e velho npm. Entre no terminal e digite:

    npm install dayjs --save

    Feito isso, já conseguimos trabalhar com ela. O primeiro método que precisamos saber é o dayjs(). Uma vez invocado sem parâmetros, ele nos retorna a data no momento em que ele foi invocado.

    Press enter or click to view image in full size

    A partir daqui, podemos começar a encadear métodos. Por exemplo, se quisermos o ano, mês e dia separadamente, podemos usar os métodos year()month() e date(), respectivamente.

    console.log(dayjs());
    console.log(dayjs().year()); // 2018
    console.log(dayjs().month()); // 11
    console.log(dayjs().date()); // 5

    O resultado é:

    Além da visualização, a manipulação das datas também é bem tranquila através do método set(). Basta termos o objeto de data do Day.js e invocar este método passando o critério que desejamos alterar (ano, dia, mês, etc). Podemos até mesmo encadear os três para gerar a data desejada.

    console.log(dayjs().set('year',2015).set('month', 3).set('day', 25));

    O resultado é o que segue:

    Aproveitando que estamos mostrando o resultado da data, podemos dar uma olhada nos métodos e formatações de display disponíveis.

    A formatação no fundo é bem simples: basta utilizarmos o método format() com os parâmetros que definem como ele será exposto na tela. Por exemplo:

    console.log(dayjs().format()); // ISO6801
    console.log(dayjs().format('{YYYY} MM-DDTHH:mm:ssZ[Z]'));
    console.log(dayjs().format('DD/MM/YYYY'));

    O resultado será o seguinte:

    Todos os parâmetros possíveis para formatação estão neste link.

    Conclusão

    Bibliotecas, frameworks e afins são como ferramentas em uma caixa: quanto mais opções, melhor. Cabe ao desenvolvedor saber qual usar em cada uma das situações.

    O Moment.js se consagrou como uma biblioteca para manipular datas e horários, mas é importante conhecer alternativas, e o Day.js é uma excelente opção.

    Referências

  • VSCode + Python + Alexa: Desenvolva e teste skills para alexa localmente com python

    VSCode + Python + Alexa: Desenvolva e teste skills para alexa localmente com python

    Ferramentas como Voiceflow são incríveis para design e prototipação de skills e muitas vezes até para o desenvolvimento do produto final. O que acontece é que o desenvolvedor pode precisar de mais liberdade para criar suas skills e acaba tropeçando em algumas barreiras em ferramentas lowcode como essa.

    Uma solução é implementar sua skill em Node ou Python usando a plataforma web da própria Amazon no developer console. Isso também pode gerar alguns desgastes já que não há versionamento de código, branchs separadas, trabalho paralelo ou algumas das outras das técnicas usadas no desenvolvimento de projetos atualmente.

    Para tentar minimizar esse problema, apresento hoje uma solução no VSCode que te ajuda a desenvolver e testar localmente suas skills. Vou colocar o passo a passo para fazer um pequeno projeto e não vou levar em conta a parte de conceitos mas se quiser saber um pouco sobre utterances, intents, slots, prompts e mais, você poderá conferir nessa minha apresentação AQUI.

    Baixar a extensão Alexa Skills Kit (ASK) Toolkit

    A primeira coisa é baixar a extensão Alexa Skills Kit (ASK) Toolkit. É uma extensão poderosa com muitas ferramentas pra sincronizar com a sua conta no developer console da amazon. Depois de instalado deverá aparecer no canto esquerdo um ícone igual ao da alexa.

    Press enter or click to view image in full size

    Clicando em “Sign in” para fazer o login, seu navegador vai abrir a tela de login da amazon. Uma vez logado o VSCode já estará sincronizado com sua conta de desenvolvedor na Alexa.

    Press enter or click to view image in full size
    Press enter or click to view image in full size

    Criar uma skill do zero

    Temos algumas opções aqui, inclusive para baixar uma skill para trabalho em paralelo mas nesse exemplo vamos criar uma do zero. O nome da skill é fala animal e vai simplesmente imitar um animal que o usuário vai requisitar.

    Depois de confirmar todos os dados essa etapa pode levar alguns minutos….

    Finalizado esse processo, podemos conferir nas imagens abaixo a estrutura do projeto que é a mesma estrutura que foi criada e poderá ser acessa via developer console na Amazon e a imagem da direita mostro o projeto devidamente criado na minha conta.

    Press enter or click to view image in full size

    Codificando a skill

    Assim como os conceitos, não vou entrar também nos pormenores da linguagem Python e nem nas técnicas aprofundadas de como criar uma skill mas nesse vídeo aqui tem tudo isso muito bem explicado.

    Como falei anteriormente, essa skill vai imitar a fala de um cachorro, um gato ou uma vaca. Qualquer animal diferente desses uma mensagem padrão vai ser apresentada.

    Press enter or click to view image in full size

    Uma vez implementado o tratamento da Intent, é necessário configurar as utterances que vão direcionar o usuário para aquela Intent e isso deve ser feito no console da amazon.

    Press enter or click to view image in full size

    Press enter or click to view image in full size

    Testando sua skill localmente

    Agora que tudo está configurado e codificado, vamos ao teste.

    Primeiramente você precisa colocar os arquivos de debug. No VSCode >> Run >> Add Configuration e adicionar um Python File.

    Press enter or click to view image in full size
    Press enter or click to view image in full size

    Uma vez que o arquivo está adicionado, clique novamente em Run >> Add configuration e escolha a opção ASK: Alexa Skills Debugger (Python).

    Abra o terminal e rode o comando para instalar as dependências:

    pip install ask-sdk-local-debug

    Depois que todas as bibliotecas estiverem instaladas é a hora de testar a sua skill e não precisaremos voltar ao developer console.

    Acesse o novo menu com o ícone da alexa e clique em Test Skill >> Open Simulator.

    Press enter or click to view image in full size
    Press enter or click to view image in full size

    Para quem já fez skills no console developer, já identificou que essa tela é bem parecida com a tela de testes na web. Estou colocando a tela local no VSCode e a tela na web como comparação.

    Conclusão

    Aplicar técnicas mais modernas (ou nem tanto) de desenvolvimento de software na IDE web pode ser complicado, mas usando o nosso conhecido VSCode o paralelismo no desenvolvimento de skills se torna mais simples assim como o versionamento e outras práticas. Recomendo (muito) aliar esse novo conhecimento a ferramentas como o Voice Flow.

    Até mais.

  • E-mails de verificação com AWS SES + Lambda (Node.js) e Terraform: do zero ao envio

    E-mails de verificação com AWS SES + Lambda (Node.js) e Terraform: do zero ao envio

    Eu quero te levar do zero até uma solução funcional de verificação de e-mail, sem depender de servidores próprios. Vamos usar AWS SES para enviar os e-mails, DynamoDB para guardar tokens com TTL, e AWS Lambda (Node.js) para orquestrar tudo. A infraestrutura nasce com Terraform usando módulos separados (SES, DynamoDB, IAM, Lambda), mantendo o código limpo e fácil de evoluir.


    O problema e a solução

    Solução

    Toda aplicação que cria contas precisa verificar e-mails. Fazer isso na unha costuma gerar acoplamento, scripts soltos e dor de cabeça com credenciais. Aqui eu:

    • Provisiono a infraestrutura como código.
    • Centralizo envios no SES com remetente verificado.
    • Persisto tokens no DynamoDB com expiração automática via TTL.
    • Exponho uma Lambda com Function URL que:
      • POST /send: gera token, salva no DynamoDB e envia e-mail de verificação.
      • GET /verify?email=...&token=...: valida token e marca como verificado.

    Resultado: um fluxo completo, serverless, barato e pronto para plugar no front-end.

    Observação importante: se sua conta SES estiver em sandbox, você precisa verificar o remetente e também os destinatários de teste, ou usar o mailbox simulator. Quando for para produção, peça saída de sandbox.


    Arquitetura (visão rápida)

    • Lambda (Node.js) com Function URL pública.
    • SES com identidade de e-mail verificada (remetente).
    • DynamoDB com chave composta pk (e-mail) + sk (token) e expiresAt como TTL.
    • IAM Roles/Policies mínimos: DynamoDB R/W na tabela e SES SendEmail.

    Pré-requisitos

    • Node.js 18+ (usarei runtime nodejs20.x na Lambda).
    • Terraform 1.6+.
    • AWS CLI configurado.
    • Uma caixa de e-mail para verificar como remetente no SES.

    Estrutura de pastas

    ses-dynamodb-lambda-verification/
      infra/
        main.tf
        variables.tf
        outputs.tf
        modules/
          ses/
            main.tf
            variables.tf
            outputs.tf
          dynamodb/
            main.tf
            variables.tf
            outputs.tf
          iam/
            main.tf
            variables.tf
            outputs.tf
          lambda/
            main.tf
            variables.tf
            outputs.tf
      app/
        package.json
        src/
          core/env.js
          domain/types.js
          services/email/email-client.js
          services/email/email-service.js
          services/token/token-service.js
          services/store/repository.js
          usecases/send-verification.js
          usecases/verify-token.js
          handler.js
        build.sh
    

    Eu separei responsabilidades por módulos Terraform e camadas de código na Lambda, seguindo SOLID/DRY/YAGNI.


    Infra com Terraform

    1) Root: infra/main.tf

    terraform {
      required_version = ">= 1.6.0"
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.50"
        }
      }
    }
    
    provider "aws" {
      region = var.aws_region
    }
    
    locals {
      name_prefix = "${var.project_name}-${var.environment}"
    }
    
    module "ses" {
      source        = "./modules/ses"
      sender_email  = var.sender_email
      name_prefix   = local.name_prefix
    }
    
    module "dynamodb" {
      source        = "./modules/dynamodb"
      table_name    = "${local.name_prefix}-verifications"
    }
    
    module "iam" {
      source              = "./modules/iam"
      name_prefix         = local.name_prefix
      dynamodb_table_arn  = module.dynamodb.table_arn
      ses_send_actions    = ["ses:SendEmail", "ses:SendRawEmail"]
    }
    
    module "lambda" {
      source          = "./modules/lambda"
      name_prefix     = local.name_prefix
      role_arn        = module.iam.lambda_role_arn
      artifact_path   = var.lambda_artifact_path
      env_map = {
        TABLE_NAME        = module.dynamodb.table_name
        SES_SENDER        = var.sender_email
        VERIFY_BASE_URL   = var.verify_base_url
        TOKEN_TTL_SECONDS = tostring(var.token_ttl_seconds)
        AWS_NODEJS_CONNECTION_REUSE_ENABLED = "1"
      }
    }
    
    output "function_url" {
      value       = module.lambda.function_url
      description = "URL pública da Lambda"
    }
    

    2) Root: infra/variables.tf

    variable "aws_region" { type = string }
    variable "project_name" { type = string }
    variable "environment" { type = string }
    variable "sender_email" { type = string }
    variable "verify_base_url" { type = string, default = "" }
    variable "lambda_artifact_path" { type = string }
    variable "token_ttl_seconds" { type = number, default = 900 }
    

    3) Root: infra/outputs.tf

    output "table_name" { value = module.dynamodb.table_name }
    output "function_url" { value = module.lambda.function_url }
    

    Módulo SES: infra/modules/ses/main.tf

    variable "sender_email" { type = string }
    variable "name_prefix"  { type = string }
    
    resource "aws_sesv2_email_identity" "this" {
      email_identity = var.sender_email
    }
    
    output "identity_arn" {
      value = aws_sesv2_email_identity.this.arn
    }
    

    A identidade de e-mail do SES v2 é gerenciada pelo recurso aws_sesv2_email_identity.


    Módulo DynamoDB: infra/modules/dynamodb/main.tf

    variable "table_name" { type = string }
    
    resource "aws_dynamodb_table" "this" {
      name         = var.table_name
      billing_mode = "PAY_PER_REQUEST"
      hash_key     = "pk"
      range_key    = "sk"
    
      attribute { name = "pk" type = "S" }
      attribute { name = "sk" type = "S" }
    
      ttl {
        attribute_name = "expiresAt"
        enabled        = true
      }
    }
    
    output "table_name" { value = aws_dynamodb_table.this.name }
    output "table_arn"  { value = aws_dynamodb_table.this.arn }
    

    O bloco ttl { attribute_name = "expiresAt" enabled = true } habilita expiração automática de itens. A remoção é assíncrona e não consome WCU.


    Módulo IAM: infra/modules/iam/main.tf

    variable "name_prefix"         { type = string }
    variable "dynamodb_table_arn"  { type = string }
    variable "ses_send_actions"    { type = list(string) }
    
    data "aws_iam_policy_document" "assume" {
      statement {
        actions = ["sts:AssumeRole"]
        principals { type = "Service" identifiers = ["lambda.amazonaws.com"] }
      }
    }
    
    resource "aws_iam_role" "lambda" {
      name               = "${var.name_prefix}-lambda-role"
      assume_role_policy = data.aws_iam_policy_document.assume.json
    }
    
    data "aws_iam_policy_document" "lambda_policy" {
      statement {
        actions   = var.ses_send_actions
        resources = ["*"]
      }
      statement {
        actions   = ["dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:UpdateItem"]
        resources = [var.dynamodb_table_arn]
      }
      statement {
        actions   = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"]
        resources = ["*"]
      }
    }
    
    resource "aws_iam_policy" "lambda_inline" {
      name   = "${var.name_prefix}-lambda-policy"
      policy = data.aws_iam_policy_document.lambda_policy.json
    }
    
    resource "aws_iam_role_policy_attachment" "attach" {
      role       = aws_iam_role.lambda.name
      policy_arn = aws_iam_policy.lambda_inline.arn
    }
    
    output "lambda_role_arn" { value = aws_iam_role.lambda.arn }
    

    Módulo Lambda: infra/modules/lambda/main.tf

    variable "name_prefix"   { type = string }
    variable "role_arn"      { type = string }
    variable "artifact_path" { type = string }
    variable "env_map"       { type = map(string) }
    
    resource "aws_lambda_function" "app" {
      function_name = "${var.name_prefix}-verification"
      role          = var.role_arn
      filename      = var.artifact_path
      handler       = "handler.handler"
      runtime       = "nodejs20.x"
      timeout       = 10
      memory_size   = 256
    
      environment {
        variables = var.env_map
      }
    
      source_code_hash = filebase64sha256(var.artifact_path)
    }
    
    resource "aws_lambda_function_url" "public" {
      function_name      = aws_lambda_function.app.function_name
      authorization_type = "NONE"
    }
    
    output "function_name" { value = aws_lambda_function.app.function_name }
    output "function_url"  { value = aws_lambda_function_url.public.function_url }
    

    Function URL deixa a Lambda acessível por HTTP diretamente, com authorization_type = "NONE" para simplificar o teste.


    Lambda (Node.js) — código de aplicação

    app/package.json

    {
      "name": "email-verification",
      "version": "1.0.0",
      "type": "module",
      "main": "src/handler.js",
      "scripts": {
        "zip": "rm -f function.zip && cd app && zip -r ../function.zip . -x \"*.zip\""
      },
      "dependencies": {
        "@aws-sdk/client-sesv2": "^3.640.0",
        "@aws-sdk/client-dynamodb": "^3.640.0",
        "@aws-sdk/lib-dynamodb": "^3.640.0"
      }
    }
    

    A Lambda usa SESv2 com SendEmailCommand do SDK v3.

    app/src/core/env.js

    export const env = {
      tableName: process.env.TABLE_NAME,
      sender: process.env.SES_SENDER,
      verifyBaseUrl: process.env.VERIFY_BASE_URL || "",
      tokenTtlSeconds: Number(process.env.TOKEN_TTL_SECONDS || "900")
    };
    

    app/src/domain/types.js

    export const Status = Object.freeze({ Pending: "pending", Verified: "verified" });
    

    app/src/services/token/token-service.js

    import crypto from "crypto";
    
    export function generateToken() {
      return crypto.randomBytes(32).toString("base64url");
    }
    
    export function expirationEpoch(secondsFromNow) {
      const now = Math.floor(Date.now() / 1000);
      return now + secondsFromNow;
    }
    

    app/src/services/email/email-client.js

    import { SESv2Client } from "@aws-sdk/client-sesv2";
    
    export function makeSes(region) {
      return new SESv2Client({ region });
    }
    

    app/src/services/email/email-service.js

    import { SendEmailCommand } from "@aws-sdk/client-sesv2";
    
    function subject() {
      return "Confirme seu e-mail";
    }
    
    function htmlBody(link, token) {
      return link
        ? `<p>Obrigado por se cadastrar.</p><p>Clique para verificar: <a href="${link}">${link}</a></p><p>Ou use este token: <b>${token}</b></p>`
        : `<p>Obrigado por se cadastrar.</p><p>Use este token para verificar: <b>${token}</b></p>`;
    }
    
    function textBody(link, token) {
      return link
        ? `Obrigado por se cadastrar.\nVerifique seu e-mail: ${link}\nToken: ${token}`
        : `Obrigado por se cadastrar.\nToken de verificação: ${token}`;
    }
    
    export async function sendVerificationEmail({ ses, sender, to, link, token }) {
      const input = {
        FromEmailAddress: sender,
        Destination: { ToAddresses: [to] },
        Content: {
          Simple: {
            Subject: { Data: subject() },
            Body: {
              Html: { Data: htmlBody(link, token) },
              Text: { Data: textBody(link, token) }
            }
          }
        }
      };
      await ses.send(new SendEmailCommand(input));
    }
    

    app/src/services/store/repository.js

    import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
    import { DynamoDBDocumentClient, PutCommand, GetCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
    
    export function makeRepo(tableName) {
      const ddb = new DynamoDBClient({});
      const doc = DynamoDBDocumentClient.from(ddb);
      return {
        async putPending({ email, token, expiresAt }) {
          const item = { pk: email, sk: token, status: "pending", expiresAt };
          await doc.send(new PutCommand({ TableName: tableName, Item: item, ConditionExpression: "attribute_not_exists(pk) AND attribute_not_exists(sk)" }));
          return item;
        },
        async getOne({ email, token }) {
          const res = await doc.send(new GetCommand({ TableName: tableName, Key: { pk: email, sk: token } }));
          return res.Item || null;
        },
        async markVerified({ email, token }) {
          await doc.send(new UpdateCommand({
            TableName: tableName,
            Key: { pk: email, sk: token },
            UpdateExpression: "SET #s = :v",
            ExpressionAttributeNames: { "#s": "status" },
            ExpressionAttributeValues: { ":v": "verified" }
          }));
        }
      };
    }
    

    app/src/usecases/send-verification.js

    import { generateToken, expirationEpoch } from "../services/token/token-service.js";
    
    export async function sendVerification({ repo, emailService, email, baseUrl, ttlSeconds, sender }) {
      const token = generateToken();
      const expiresAt = expirationEpoch(ttlSeconds);
      await repo.putPending({ email, token, expiresAt });
    
      const link = baseUrl ? `${baseUrl.replace(/\/$/, "")}/verify?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}` : "";
      await emailService.send({ to: email, token, link, sender });
      return { email, expiresAt, linkPresent: Boolean(link) };
    }
    

    app/src/usecases/verify-token.js

    export async function verifyToken({ repo, email, token }) {
      const item = await repo.getOne({ email, token });
      if (!item) return { ok: false, reason: "invalid" };
      if (item.expiresAt && item.expiresAt < Math.floor(Date.now() / 1000)) return { ok: false, reason: "expired" };
      await repo.markVerified({ email, token });
      return { ok: true };
    }
    

    app/src/handler.js

    import { env } from "./core/env.js";
    import { makeRepo } from "./services/store/repository.js";
    import { makeSes } from "./services/email/email-client.js";
    import * as emailSvc from "./services/email/email-service.js";
    import { sendVerification } from "./usecases/send-verification.js";
    import { verifyToken } from "./usecases/verify-token.js";
    
    const repo = makeRepo(env.tableName);
    const ses = makeSes(process.env.AWS_REGION);
    
    const emailService = {
      send: ({ to, token, link, sender }) => emailSvc.sendVerificationEmail({ ses, sender: sender || env.sender, to, link, token })
    };
    
    function json(status, body, headers = {}) {
      return { statusCode: status, headers: { "content-type": "application/json", ...headers }, body: JSON.stringify(body) };
    }
    
    export async function handler(event) {
      const rawPath = event.rawPath || "/";
      const method = event.requestContext?.http?.method || "GET";
    
      if (method === "POST" && rawPath.endsWith("/send")) {
        const body = typeof event.body === "string" ? JSON.parse(event.body || "{}") : event.body || {};
        if (!body?.email) return json(400, { error: "email_required" });
    
        const result = await sendVerification({
          repo,
          emailService,
          email: body.email,
          baseUrl: env.verifyBaseUrl,
          ttlSeconds: env.tokenTtlSeconds,
          sender: env.sender
        });
        return json(202, { email: result.email, expiresAt: result.expiresAt, linkIncluded: result.linkPresent });
      }
    
      if (method === "GET" && rawPath.endsWith("/verify")) {
        const qs = event.rawQueryString || "";
        const params = Object.fromEntries(new URLSearchParams(qs).entries());
        const email = params.email || "";
        const token = params.token || "";
        if (!email || !token) return json(400, { error: "email_and_token_required" });
    
        const res = await verifyToken({ repo, email, token });
        if (!res.ok) return json(400, { verified: false, reason: res.reason });
        return json(200, { verified: true });
      }
    
      return json(404, { error: "not_found" });
    }
    

    Build do artefato da Lambda

    app/build.sh

    #!/usr/bin/env bash
    set -euo pipefail
    cd "$(dirname "$0")"
    npm ci
    cd ..
    rm -f function.zip
    cd app
    zip -r ../function.zip . -x "*.zip"
    

    Esse zip contém src/ e node_modules. O handler é handler.handler como configurado.


    Deploy: passo a passo

    1. Ajuste variáveis:

    Crie um arquivo infra/terraform.tfvars:

    aws_region         = "us-east-1"
    project_name       = "email-verify"
    environment        = "dev"
    sender_email       = "seu-remetente@dominio.com"
    lambda_artifact_path = "../function.zip"
    token_ttl_seconds  = 900
    verify_base_url    = "" # deixe vazio por agora, ou coloque a URL que fará o GET /verify
    
    1. Verifique/prepare o remetente no SES (console) ou com Terraform do módulo ses. Se a conta estiver em sandbox, verifique também o destinatário de teste.
    2. Gere o pacote da Lambda:
    bash app/build.sh
    
    1. Aplique Terraform:
    cd infra
    terraform init
    terraform apply -auto-approve
    
    1. Copie a Function URL de saída.

    Se quiser que o e-mail traga o link direto de verificação apontando para a própria Lambda, defina verify_base_url igual à Function URL e rode:

    terraform apply -var="verify_base_url=$(terraform output -raw function_url)" -auto-approve
    

    Como testar (curl, Postman e AWS CLI)

    1) Disparar e-mail de verificação

    FUNCTION_URL=$(terraform -chdir=infra output -raw function_url)
    curl -s -X POST "$FUNCTION_URL/send" \
      -H "content-type: application/json" \
      -d '{"email":"destinatario@exemplo.com"}' | jq
    

    Resposta esperada:

    {
      "email": "destinatario@exemplo.com",
      "expiresAt": 1724099999,
      "linkIncluded": true
    }
    

    Se verify_base_url estiver vazio, linkIncluded será false e o corpo do e-mail trará apenas o token.

    O envio usa a operação SESv2 SendEmail do SDK v3.

    2) Validar o token

    Pegue o link no e-mail (se configurado) ou recupere o token no próprio e-mail. Então:

    curl -s "$FUNCTION_URL/verify?email=destinatario@exemplo.com&token=SEU_TOKEN" | jq
    

    Resposta esperada em sucesso:

    { "verified": true }
    

    Falhas retornam HTTP 400 com reason igual a invalid ou expired.

    3) Conferir no DynamoDB com AWS CLI

    aws dynamodb get-item \
      --table-name $(terraform -chdir=infra output -raw table_name) \
      --key '{"pk":{"S":"destinatario@exemplo.com"}, "sk":{"S":"SEU_TOKEN"}}'
    

    Você vai ver status mudando para verified. Se estiver aguardando expiração, lembre que o TTL é assíncrono; a remoção pode levar algum tempo.


    Custos, limites e produção

    • DynamoDB com PAY_PER_REQUEST e TTL tem custo muito baixo no cenário de verificação.
    • Lambda é cobrada por invocação e tempo de execução, tipicamente centavos.
    • SES em sandbox limita destinatários e throughput; para produção, solicite saída de sandbox.
    • Para domínios próprios, prefira identidade de domínio e configure DKIM/SPF; aqui usei identidade por e-mail para simplificar o início.

    Próximos passos

    • Substituir a Function URL por API Gateway com authorizers.
    • Adicionar template HTML rico e Configuration Set do SES (monitoramento, IP pool gerenciado).
    • Colocar a verificação atrás de um domínio customizado e TLS, com CloudFront + Function URL ou API Gateway.

    Com isso, você tem um pipeline completo de verificação de e-mail, IaC de ponta a ponta e uma Lambda enxuta pronta para conectar ao seu front-end.


    Referências

    Daniel, G. (2025, August 9). AWS SES with a NestJS Backend to Send Email Verifications. DEV Community. https://dev.to/aws-builders/aws-ses-with-a-nestjs-backend-to-send-email-verifications-2l9h (DEV Community)

    Amazon Web Services. (n.d.). Request production access (Moving out of the Amazon SES sandbox). In Amazon Simple Email Service Developer Guidehttps://docs.aws.amazon.com/ses/latest/dg/request-production-access.html (AWS Documentation)

    Amazon Web Services. (n.d.). Verified identities in Amazon SES. In Amazon Simple Email Service Developer Guidehttps://docs.aws.amazon.com/ses/latest/dg/verify-addresses-and-domains.html (AWS Documentation)

    Amazon Web Services. (n.d.). Using time to live (TTL) in DynamoDB. In Amazon DynamoDB Developer Guidehttps://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html (AWS Documentation)

    HashiCorp. (n.d.). aws_lambda_function_url (Resource)Terraform Registryhttps://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_url.html (Terraform Registry)

    Amazon Web Services. (n.d.). Creating and managing Lambda function URLs. In AWS Lambda Developer Guidehttps://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html (AWS Documentation)

    Amazon Web Services. (n.d.). SendEmailCommand (SESv2). In AWS SDK for JavaScript v3 API Referencehttps://docs.aws.amazon.com/goto/SdkForJavaScriptV3/sesv2-2019-09-27/SendEmail (AWS Documentation)

    Amazon Web Services. (n.d.). SESv2Client. In AWS SDK for JavaScript v3https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/sesv2 (AWS Documentation)

  • Conhecendo o SOC – Separation of Concerns

    Veja neste artigo o que é o SOC, um dos princípios fundamentais muito utilizado na abordagem das arquiteturas modernas.

    Exemplo de um diagrama com SOC

    Se você já se deparou com um código que parecia fazer “tudo ao mesmo tempo” como: processar dados, exibir informações na tela, salvar no banco de dados e ainda validar entradas do usuário, então você já experimentou na prática o que acontece quando não aplicamos o princípio da Separation of Concerns (Separação de Responsabilidades).

    Este é um dos princípios mais fundamentais da engenharia de software, e dominar esse conceito é essencial para qualquer desenvolvedor que queira escrever código limpo, maintível e escalável.

    Mas afinal o que seria o SOC?

    Separation of Concerns é um princípio de design que sugere que cada módulo, classe ou função deve ter uma única responsabilidade e deve ser responsável apenas por uma área específica de funcionalidade.

    Em termos simples: cada parte do seu código deve ter apenas uma razão para mudar.

    Fazendo uma analogia do mundo real (apenas simulando o mundo ideal), imagine uma cozinha de um restaurante.

    Você não encontrará um único chef fazendo tudo, cada profissional tem sua especialidade e responsabilidade específica. Se o chef de sobremesas precisar mudar uma receita, isso não afeta o trabalho do chef de grill.

    Esta abordagem nos tras os seguintes beneficios?

    • Quando cada componente tem uma responsabilidade específica, é muito mais fácil localizar e corrigir problemas.
    • Componentes com responsabilidades bem definidas são mais fáceis de testar isoladamente.
    • Componentes especializados podem ser reutilizados em diferentes contextos.
    • Componentes independentes são menos dependentes uns dos outros.
    • Diferentes desenvolvedores podem trabalhar em diferentes componentes simultaneamente.

    O Separation of Concerns não é apenas teoria, ele é uma ferramenta prática que transforma a qualidade do seu código. Começar a aplicar este princípio pode parecer trabalhoso inicialmente, mas os benefícios se tornam evidentes conforme seus projetos crescem.

     

  • IA e Machine Learning com Swift

    IA e Machine Learning com Swift

    Foco crescente na integração de recursos básicos de ML e IA em aplicativos iOS, com artigos sobre o uso do Swift para tarefas como processamento de linguagem natural e reconhecimento de imagem em tempo real.

    IA e aprendizado de máquina com Swift: integrando Core ML em aplicativos iOS

    Em 2023, a integração de inteligência artificial (IA) e aprendizado de máquina (ML) em aplicativos iOS ganhou um impulso significativo. Utilizando o Swift em conjunto com o Core ML, a estrutura de aprendizado de máquina da Apple, os desenvolvedores agora podem criar recursos avançados baseados em IA, como processamento de linguagem natural (PLN) e reconhecimento de imagens em tempo real. Este artigo explorará como integrar o Core ML em aplicativos iOS usando o Swift, com exemplos práticos e explicações sobre como essas tecnologias funcionam em conjunto.

    Visão geral do Core ML

    O Core ML é uma estrutura poderosa fornecida pela Apple que permite aos desenvolvedores integrar modelos de aprendizado de máquina treinados em seus aplicativos iOS. Com o Core ML, você pode executar tarefas como classificação de imagens, detecção de objetos e tradução de idiomas diretamente no dispositivo, aproveitando seus recursos computacionais.

    Principais recursos do Core ML:

    • Integração de modelos : integre facilmente modelos de aprendizado de máquina ao seu aplicativo.
    • Otimização de desempenho : o Core ML otimiza modelos para desempenho no hardware da Apple.
    • Suporte para vários tipos de modelos : funciona com modelos treinados usando várias estruturas como TensorFlow, PyTorch e scikit-learn.

    Integrando Core ML com Swift

    Vamos nos aprofundar em como usar o Core ML com Swift para adicionar recursos de IA ao seu aplicativo iOS. Abordaremos dois casos de uso comuns: processamento de linguagem natural e reconhecimento de imagens em tempo real.

    1. Processamento de Linguagem Natural (PLN)

    O NLP permite que aplicativos entendam e processem a linguagem humana. O Core ML fornece ferramentas para análise de sentimentos, identificação de linguagem e muito mais.

    Exemplo: Análise de Sentimentos

    Suponha que você queira analisar o sentimento das avaliações dos usuários no seu aplicativo. Você pode usar um modelo de análise de sentimento pré-treinado e integrá-lo ao seu aplicativo.

    Guia passo a passo:

    1. Obtenha um modelo pré-treinado : baixe um modelo de análise de sentimento compatível com o Core ML, como SentimentModel.mlmodel.
    2. Adicione o modelo ao seu projeto Xcode : arraste o arquivo . mlmodel para o seu projeto Xcode.
    3. Gerar código Swift : o Xcode gera automaticamente o código Swift para o modelo.
    4. Use o modelo em seu código :
    import CoreML 
    import NaturalLanguage 
    
    // Carregue o modelo 
    guard  let sentimentModel =  try?  SentimentModel (configuration: MLModelConfiguration ()) else { 
        fatalError ( "Falha ao carregar o modelo" ) 
    } 
    
    // Preveja o sentimento 
    func  analyzeSentiment ( of  text : String ) -> String { 
        let prediction =  try? sentimentModel.prediction (text: text) 
        switch prediction ? .label { 
        case  "Positive" : 
            return  "O sentimento é positivo." 
        case  "Negative" : 
            return  "O sentimento é negativo." 
        default : 
            return  "Não foi possível determinar o sentimento."
         } 
    } 
    
    // Exemplo de uso 
    let reviewText =  "Adoro usar este aplicativo! É fantástico." 
    print (analyzeSentiment (of: reviewText))

    Neste código, SentimentModel é a classe gerada automaticamente a partir do seu arquivo .mlmodel . A função analyzeSentiment usa esse modelo para prever o sentimento do texto fornecido.

    2. Reconhecimento de imagem em tempo real

    O reconhecimento de imagens envolve a classificação e a identificação de objetos dentro de imagens. O Core ML pode ser usado para tarefas como detecção de objetos e classificação de imagens.

    Exemplo: Detecção de Objetos

    Suponha que você queira detectar objetos em imagens capturadas pela câmera. Veja como integrar um modelo de detecção de objetos pré-treinado.

    Guia passo a passo:

    1. Obtenha um modelo pré-treinado : baixe um modelo de detecção de objetos compatível com o Core ML, como YOLOv3.mlmodel.
    2. Adicione o modelo ao seu projeto Xcode : arraste o arquivo .mlmodel para o seu projeto Xcode.
    3. Gerar código Swift : o Xcode gera código Swift para o modelo automaticamente.
    4. Use o modelo em seu código :
    importar CoreML 
    importar Vision 
    importar UIKit 
    
    // Carregar o modelo 
    de proteção  let model =  try?  VNCoreMLModel (for: YOLOv3 ().model) else { 
        fatalError ( "Falha ao carregar o modelo" ) 
    } 
    
    // Criar uma solicitação para detecção de objeto 
    let request =  VNCoreMLRequest (model: model) { solicitação, erro em 
        if  let results = request.results as? [ VNRecognizedObjectObservation ] { 
            para resultado em resultados { 
                let boundingBox = result.boundingBox 
                let label = result.labels.first ? .identifier ??  "Desconhecido" 
                print ( "Objeto detectado: \(label) com caixa delimitadora: \(boundingBox) " ) 
            } 
        } 
    } 
    
    // Executar a solicitação 
    func  detectObjects ( in  image : UIImage ) { 
        let handler =  VNImageRequestHandler (cgImage: image.cgImage ! ) 
        try? handler.perform([request]) 
    } 
    
    // Exemplo de uso 
    if  let image =  UIImage (named: "exampleImage.jpg" ) { 
        detectObjects(in: image) 
    }

    Neste código, YOLOv3 é a classe gerada automaticamente a partir do seu arquivo .mlmodel . A função detectObjects usa o framework Vision para manipular o modelo Core ML e realizar a detecção de objetos em uma imagem.

    Melhores práticas para integração de IA e ML

    Ao integrar recursos de IA e ML em seus aplicativos iOS, considere as seguintes práticas recomendadas:

    1. Otimização do Modelo : Garanta que seus modelos estejam otimizados para desempenho em dispositivos móveis. Use técnicas de quantização e poda para reduzir o tamanho do modelo e melhorar a velocidade de inferência.
    2. Considerações sobre privacidade : preste atenção à privacidade do usuário. Sempre que possível, realize cálculos confidenciais localmente no dispositivo para evitar o envio de dados pessoais para servidores externos.
    3. Experiência do usuário : crie a interface do usuário do seu aplicativo para lidar com recursos baseados em IA com elegância, fornecendo feedback e contexto aos usuários sobre os recursos e limitações da IA.
    4. Testes e Validação : Teste exaustivamente os recursos de IA sob diversas condições para garantir precisão e confiabilidade. Valide e atualize seus modelos continuamente, conforme necessário.

    Conclusão

    A integração de IA e aprendizado de máquina em aplicativos iOS está transformando a forma como os desenvolvedores criam aplicativos inteligentes e responsivos. Com Swift e Core ML, os desenvolvedores podem aproveitar o poder do aprendizado de máquina para processamento de linguagem natural, reconhecimento de imagens e muito mais. Os exemplos fornecidos ilustram como é fácil incorporar recursos avançados de IA em seus aplicativos, aprimorando sua funcionalidade e a experiência do usuário.

    À medida que as tecnologias de IA e ML continuam a evoluir, manter-se atualizado com os últimos avanços e melhores práticas permitirá que você crie aplicativos de ponta que aproveitem todo o potencial do Swift e do Core ML.

    Boa programação e boa exploração do mundo da IA!

  • Amigo, gerente de projetos. O seu projeto terminou, mas ninguém usa? Bem-vindo à era do Product Thinking

    Amigo, gerente de projetos. O seu projeto terminou, mas ninguém usa? Bem-vindo à era do Product Thinking

    Quando percebi que entregar no prazo não era o suficiente

    Durante boa parte da minha carreira, eu me orgulhava de entregar projetos dentro do prazo e do orçamento. Cada check verde no cronograma era uma pequena vitória.

    Mas, com o tempo, comecei a perceber um incômodo: alguns desses projetos “bem-sucedidos” simplesmente não geravam resultado algum para o negócio.

    Lembro de um caso específico: um sistema interno que desenvolvemos com toda a metodologia clássica , escopo fechado, sprints bem conduzidas, testes validados.

    O projeto foi entregue impecável. Três meses depois, o uso era mínimo.

    O time havia entregue o que foi pedido, mas não o que realmente resolveria o problema. Foi um choque. Ali percebi a diferença entre pensar como Project Manager e pensar como Product Thinker.

    No primeiro caso, meu foco era concluir o trabalho. No segundo, deveria ter sido entender o porquê daquele trabalho existir.

    O que muda quando passamos a pensar como donos de produto

    Pensar como um Product Manager não é mudar de função , é mudar de lente.
    Como PM, eu costumava medir sucesso em marcos e entregas. Hoje, aprendi a perguntar: “qual valor isso gera?”

    O foco deixa de ser o output (entregas) e passa a ser o outcome (impacto).

     

    E dá pra resumir a diferença nesse “processinho básico” que me ajudou a virar a chave 👇

    E, no fundo, é até engraçado: enquanto o Project Thinking tenta controlar tudo, o Product Thinking tenta entender o porquê.

    Um mede progresso; o outro mede propósito. Um comemora checklists; o outro comemora aprendizado.

    É a diferença entre “terminamos o projeto” e “valeu a pena ter feito?”.

    Entregar menos, mas entregar melhor

    Num projeto recente, o time de negócios chegou com uma lista: dez novas funcionalidades para “melhorar a experiência do cliente”.

    Sabe aquele momento em que você olha pra lista e já vê um cronograma pedindo socorro? 

    😅 Pois é.

    Em vez de sair planejando tudo, resolvemos dar um passo pra trás. Fomos entender o que realmente doía para o cliente. Descobrimos que duas pequenas melhorias resolviam 80% das reclamações.

    Entregamos só essas duas e o impacto foi imediato. Menos features. Mais valor. Mais sorrisos.

    Desde então, carrego comigo uma regra simples: “Nem todo escopo precisa nascer, mas todo valor precisa aparecer.”

    Gerenciar projetos, hoje, é menos sobre controlar tarefas e mais sobre curadoria com  propósito. E quem entende isso, deixa de ser apenas gerente e vira guardião de impacto.

     

  • Idempotência em Sistemas Distribuídos: Garantindo Consistência em APIs e Mensageria

    Idempotência em Sistemas Distribuídos: Garantindo Consistência em APIs e Mensageria

    A idempotência é um conceito fundamental em sistemas distribuídos, especialmente em APIs e mensageria. Trata-se da propriedade que garante que uma operação pode ser aplicada várias vezes sem alterar o resultado além da aplicação inicial. Este artigo explora a importância da idempotência, suas aplicações práticas e fornece exemplos em C# para ilustrar como implementá-la em sistemas modernos.

    Conceitos Básicos de Idempotência

    Definição de Idempotência

    A idempotência é definida como a capacidade de realizar uma operação múltiplas vezes sem que o efeito final mude após a primeira aplicação (HUMMER et al., 2013). Em sistemas distribuídos, isso é crucial para evitar efeitos colaterais indesejados, especialmente em operações que envolvem escrita de dados.

    Fluxograma de um processo de exclusão contendo Idempotência

    Imagem SVG do Artigo

    Importância em Sistemas Distribuídos

    Em sistemas distribuídos, falhas de rede, latência e retries são comuns. Se uma operação não for idempotente, a repetição pode levar a inconsistências nos dados. Por exemplo, ao processar um pagamento, se um pedido for enviado duas vezes, o resultado pode ser a cobrança duplicada do cliente. A idempotência garante que, mesmo que a operação seja repetida, o estado final será o mesmo.

    Idempotência em APIs REST

    Princípios de Design

    APIs REST geralmente seguem o princípio de que métodos HTTP como GET e DELETE devem ser idempotentes (CDEMI, 2025). Por exemplo, um DELETE deve garantir que a remoção de um recurso seja segura, mesmo que o pedido seja enviado várias vezes.

    Implementação em C#

    Segue um exemplo de uma API REST em ASP.NET que implementa um método DELETE idempotente:

    public class ProductController : ControllerBase
    {
        private readonly IProductRepository _productRepository;
    
        public ProductController(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }
    
        [HttpDelete("products/{id}")]
        public IActionResult DeleteProduct(Guid id)
        {
            var product = _productRepository.GetProductById(id);
    
            if (product == null)
            {
                return NotFound();
            }
    
            _productRepository.DeleteProduct(id);
            return NoContent();
        }
    }

    Idempotência em Mensageria

    Desafios de Mensageria

    RabbitMQ é um dos brokers de mensagens mais utilizados no mercado, construído sobre o protocolo AMQP (Advanced Message Queuing Protocol). Sua principal função é viabilizar a comunicação assíncrona e confiável entre diferentes componentes de um sistema distribuído, fornecendo recursos como filas, roteamento flexível e mecanismos de confirmação (RABBITMQ, 2025). Graças à sua ampla adoção e robustez, o RabbitMQ tornou-se uma referência prática no estudo e na aplicação de padrões de confiabilidade em mensageria.

    Entretanto, em arquiteturas que dependem de mensageria, a idempotência surge como um desafio relevante, já que mensagens podem ser entregues mais de uma vez devido a falhas de rede, reenvios automáticos ou confirmações atrasadas. Nesses cenários, a implementação de consumidores idempotentes é essencial para assegurar que a lógica de negócios seja executada apenas uma vez, evitando inconsistências e efeitos colaterais indesejados.

    Implementação em C# com RabbitMQ

    A seguir, apresentamos um exemplo de como implementar um consumidor de mensagens idempotente utilizando RabbitMQ:

    public class MessageConsumer
    {
        private readonly IProductRepository _productRepository;
    
        public MessageConsumer(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }
    
        public void ConsumeMessage(BasicDeliverEventArgs eventArgs)
        {
            var message = Encoding.UTF8.GetString(eventArgs.Body.ToArray());
            var product = JsonConvert.DeserializeObject<Product>(message);
    
            if (!_productRepository.Exists(product.Id))
            {
                _productRepository.AddProduct(product);
            }
    
            // Acknowledge the message
            channel.BasicAck(eventArgs.DeliveryTag, false);
        }
    }

    Estratégias para Garantir Idempotência

    Uso de Identificadores Únicos

    Uma das maneiras mais comuns de garantir idempotência é o uso de identificadores únicos para operações. Cada operação deve ser associada a um ID exclusivo, que pode ser armazenado para verificar se a operação já foi processada.

    Armazenamento de Estado

    Outra estratégia é manter um estado persistente que registra as operações já realizadas. Isso permite que o sistema verifique se uma operação foi realizada antes de executá-la novamente.

    Desafios e Considerações

    Gerenciamento de Estado

    O gerenciamento de estado para garantir idempotência pode introduzir complexidade adicional ao sistema. É importante considerar como o estado será gerenciado e como as operações serão recuperadas em caso de falhas.

    Desempenho

    A implementação de idempotência pode impactar o desempenho do sistema. Por exemplo, verificar se uma operação foi realizada pode adicionar latência. Portanto, é fundamental encontrar um equilíbrio entre garantir a idempotência e manter um bom desempenho.

    Exemplos Práticos e Casos de Uso

    Processamento de Pedidos

    Em um sistema de e-commerce, o processamento de pedidos é uma área crítica onde a idempotência deve ser garantida. Se um cliente enviar um pedido e a requisição falhar, mas o pagamento já tiver sido processado, o sistema deve assegurar que o pedido não seja duplicado.

    public class OrderService
    {
        private readonly IOrderRepository _orderRepository;
    
        public OrderService(IOrderRepository orderRepository)
        {
            _orderRepository = orderRepository;
        }
    
        public void CreateOrder(Order order)
        {
            if (_orderRepository.Exists(order.Id))
            {
                // Handle duplicate order
                return;
            }
    
            _orderRepository.AddOrder(order);
        }
    }

    Transferências Bancárias

    Outro exemplo é em sistemas bancários, onde transferências devem ser idempotentes. Se uma transferência for enviada duas vezes devido a problemas de rede, o sistema deve garantir que o saldo do cliente não seja alterado mais de uma vez.

    public class TransferService
    {
        private readonly ITransferRepository _transferRepository;
    
        public TransferService(ITransferRepository transferRepository)
        {
            _transferRepository = transferRepository;
        }
    
        public void TransferFunds(FundTransfer transfer)
        {
            if (_transferRepository.Exists(transfer.Id))
            {
                // Handle duplicate transfer
                return;
            }
    
            // Logic to transfer funds
            _transferRepository.AddTransfer(transfer);
        }
    }

    Considerações Finais

    A idempotência em sistemas distribuídos é uma prática essencial para garantir a consistência e a confiabilidade das operações. Com a crescente adoção de APIs REST e sistemas de mensageria, a implementação de idempotência se torna cada vez mais relevante. Ao adotar estratégias adequadas, como o uso de identificadores únicos e o armazenamento de estado, os desenvolvedores podem construir sistemas mais robustos e resilientes.

    Referências

    • HUMMER, Waldemar et al. Testing idempotence for infrastructure as code. In: ACM/IFIP/USENIX international conference on distributed systems platforms and open distributed processing. Berlin, Heidelberg: Springer Berlin Heidelberg, 2013. p. 368-388.
    • CDEMI. Misconception on idempotency in REST APIs. Disponível em: https://blog.cdemi.io/misconception-on-idempotency-in-rest-apis/. Acesso em: set. 2025.
    • RABBITMQ. Documentation. Disponível em: https://www.rabbitmq.com/documentation.html. Acesso em: set. 2025.
  • Visual Studio Code para Java: o guia completo (dicas, configuração e extensões)

    Visual Studio Code para Java: o guia completo (dicas, configuração e extensões)

    Tenho usado o Visual Studio Code para desenvolvimento em JavaScript e TypeScript há vários anos e, mais recentemente, passei os últimos dois anos usando-o para projetos Java profissionais. Inicialmente, fiquei hesitante, mas o VSCode provou seu valor com recursos comparáveis ​​aos encontrados em IDEs que já estão no mercado há algum tempo. Embora eu ainda aprecie outros IDEs Java populares, gosto de explorar novas ferramentas. O VSCode se tornou meu editor preferido para novos projetos Java.

    Neste guia, configuraremos o Visual Studio Code para desenvolvimento Java ideal e exploraremos como maximizar seu potencial para fluxos de trabalho profissionais. Demonstrarei como o VSCode aumenta sua eficiência com extensões poderosas, ferramentas de depuração robustas e excelente suporte ao framework Spring.

    Configurando o VS Code para desenvolvimento Java e JDK

    A documentação do VSCode possui um tutorial abrangente de introdução que aborda os conceitos básicos. Recomendo fortemente que você o confira se é novo no uso do VSCode para desenvolvimento em Java. Neste guia, abordaremos alguns fundamentos e também exploraremos recursos mais avançados.

    Se você ainda não instalou o Java ou o VSCode, a maneira mais fácil de começar é com o Coding Pack for Java. Este pacote inclui o VS Code, o Java Development Kit (JDK) e uma coleção de extensões essenciais da Microsoft. Aqui estão os links:

    • Janelas: (https://aka.ms/vscode-java-installer-win)[https://aka.ms/vscode-java-installer-win]
    • Mac: (https://aka.ms/vscode-java-installer-mac)[https://aka.ms/vscode-java-installer-mac]

    Se estiver usando Linux, você precisará configurar o Java e o VSCode manualmente. Não se preocupe, abordaremos essas etapas nas próximas seções e também darei orientações caso prefira a configuração manual em outros sistemas operacionais.

    Além do editor, também precisamos instalar o Java Development Kit (JDK). Você pode instalá-lo manualmente seguindo o guia oficial neste link . No entanto, meu método preferido é usar o SDKMAN – Software Development Kit Manager , que simplifica o gerenciamento de múltiplas versões do JDK no seu sistema.

    Com o Visual Code instalado e o JDK pronto, vamos verificar sua configuração Java. Execute o seguinte comando no seu terminal:

    java -version
    

    E você deve obter uma versão como resultado:

    Instalar extensões VSCode para Java: extensões básicas

    A primeira extensão recomendada para instalação é o Extension Pack for Java da Microsoft, que inclui as seguintes extensões:

    Depois que o Extension Pack para Java estiver instalado, abra a Paleta de Comandos no VSCode e pesquise por Visão Geral do Java :

    Abra a visualização e você deverá ver algo como a tela abaixo:

    Com essa visualização, você pode instalar extensões Java adicionais, configurar atalhos (até mesmo usar os mesmos atalhos que você está acostumado em outros IDEs Java) e também acessar tutoriais.

    Vamos rever o que cada uma dessas extensões nos oferece e como usá-las.

    Suporte de linguagem para Java™ pela Red Hat

    Esta extensão fornece suporte à linguagem Java com base no Eclipse, permitindo-nos criar/abrir projetos Java, refatorar e completar código.

    Para testar, podemos criar um projeto abrindo a Paleta de Comandos no VSCode e buscando por Java new :

    Selecione a opção Java: Criar Novo Projeto . Você será solicitado a escolher o tipo de projeto que deseja criar. Selecione ” Sem ferramentas de construção” , que criará um projeto muito semelhante ao que obteríamos ao criar o projeto usando o Eclipse IDE:

    Em seguida, você precisará selecionar onde deseja que o projeto seja criado, para poder selecionar uma pasta no seu computador. Isso é semelhante ao conceito de espaço de trabalho que temos no Eclipse IDE. Em seguida, informe o nome do projeto:

    O VSCode criará uma pasta com o nome fornecido e abrirá o projeto. Você pode abrir o App.javaarquivo.

    Observe algumas coisas:

    • 1 : Ao abrir uma classe com um public static void mainmétodo, você terá uma opção Executar e Depurar para poder executar seu arquivo.
    • 2 : A saída do programa será exibida no Terminal. Você pode ver que, nos bastidores, o VSCode está compilando e executando a Appclasse, seguido pela saída do programa ( Hello, World!).
    • 3 : Você pode gerenciar o projeto na barra lateral padrão do VSCode Explorer ou na seção Projetos Java fornecida pela extensão Project Manager for Java .

    Com esta extensão, você obtém recursos muito semelhantes aos do Eclipse IDE.

    A única desvantagem que observei é que às vezes demora algumas semanas após o lançamento do Java para adicionar os novos recursos. Mas se você não tem pressa para usar os recursos mais recentes do Java, isso não será um problema.

    Configurando a versão JKD

    Por padrão, a extensão usará a versão do Java definida na sua variável de ambiente JAVA_HOME. Se, por algum motivo, você precisar modificar a versão do JDK para um projeto específico, poderá configurar uma lista de JDKs disponíveis para que possam ser facilmente atualizados em seus projetos.

    Para fazer isso, abra as configurações do VSCode e pesquise por jdk :

    Você precisará adicionar a configuração diretamente ao settings.json. Abaixo está o que eu tenho, e defini o Java 21 como a versão padrão. Você pode definir quantas versões precisar.

    "java.configuration.runtimes": [
      {
        "name": "JavaSE-17",
        "path": "/Users/loiane/.sdkman/candidates/java/17-open"
      },
      {
        "name": "JavaSE-21",
        "path": "/Users/loiane/.sdkman/candidates/java/21-oracle",
        "default": true
      }
    ]

    Para atualizar o JDK do projeto, abra a tela Visão Geral do Java novamente (via Paleta de Comandos) e selecione Configurar o Java Runtime . Os projetos que você possui no seu espaço de trabalho atual do VSCode serão abertos e um menu suspenso estará disponível para você selecionar uma versão existente que você tenha instalado:

    Você também tem a opção de baixar um JDK diretamente do VSCode clicando no link de download .

    Depurador para Java

    Conforme a descrição da extensão: “Um depurador Java leve baseado no Java Debug Server que estende o suporte à linguagem Java da Red Hat. Ele permite que os usuários depurem código Java usando o Visual Studio Code (VS Code)”.

    Este depurador funciona de forma muito semelhante a outros IDEs Java:

    • 1 : Execute seu projeto no modo de depuração para iniciar a depuração – certifique-se de abrir a visualização “Executar depuração” na barra lateral;
    • 2 : Adicione pontos de interrupção às linhas de código cuja execução você gostaria de interromper para verificar mais detalhes;
    • 3 : Você pode retomar o programa, entrar, passar por cima, reiniciar e parar a execução (semelhante às opções do Eclipse);
    • 4 : Você pode ver variáveis ​​locais e variáveis ​​globais;
    • 5 : Você também pode adicionar quais variáveis ​​ou expressões observar;
    • 6 : Também é possível digitar ou colar variáveis ​​ou expressões no Console de Depuração para verificar os resultados de uma expressão (semelhante ao modo Observar);
    • 7 : O VSCode também mostrará os valores das variáveis ​​e expressões, o que é muito útil.

    Acho a experiência muito semelhante ao Eclipse IDE, e ele abrange todas as funcionalidades que precisamos para depurar aplicativos Java.

    Gerente de Projetos para Java

    Esta extensão fornece recursos adicionais do explorador de projetos Java.

    Acho muito útil criar novos pacotes e classes (e outros tipos de arquivos Java) e gerenciar dependências para projetos simples.

    Todas as três extensões que analisamos até agora funcionam bem em conjunto com a extensão Language Support for Java by Red Hat. Analisaremos as outras extensões mais adiante neste guia com exemplos mais complexos.

    Extensão de suporte à plataforma Java da Oracle

    A extensão Java Platform Support foi lançada pela Oracle em 2023 e é uma alternativa à extensão da Red Hat. Esta extensão cria projetos baseados no NetBeans.

    A maior vantagem desta extensão é que você obtém suporte à linguagem Java, suporte ao depurador e suporte ao explorador de projetos, o que significa que é uma extensão três em um se a compararmos com as extensões do Red Hat.

    Vamos criar um novo projeto. Acesse a Paleta de Comandos , pesquise por java new e selecione Java: New Project :

    Selecione Maven ou Gradle, de acordo com sua preferência:

    Selecione o aplicativo Java:

    Digite o caminho completo onde seu aplicativo será criado, incluindo o nome do projeto:

    Digite o nome do pacote padrão:

    E este será o resultado:

    Obtemos o mesmo “Executar Links de “Depuração”, mas em vez disso, é chamado de “Executar o principal Depurar principal”.

    Quando executamos o projeto, em vez de obter a saída no Terminal, a obtemos no Terminal de Depuração.

    Também temos um explorador de projetos. No entanto, também temos uma visualização de Configuração de Execução, onde podemos passar variáveis ​​de ambiente e definir outros parâmetros.

    A experiência do depurador também é muito semelhante e tem todas as funcionalidades que podemos precisar.

    A Oracle publicou esta visão geral aprofundada destacando todos os recursos incluídos: https://youtu.be/3NSdlU22C0Q .

    No final das contas, é uma questão de preferência pessoal.

    Trabalhando com Spring Framework

    O VSCode também oferece suporte a projetos Spring Boot instalando o Spring Boot Extension Pack .

    Após a instalação do pacote de extensão, podemos criar um novo projeto. Recebemos exatamente o mesmo suporte que https://start.spring.io/ — com exceção da opção Explorar , disponível apenas na versão web.

    Pessoalmente, de todos os IDEs gratuitos, este é meu suporte favorito para projetos Spring.

    Para começar, abra novamente a Paleta de Comandos e pesquise por spring . Você pode selecionar entre projetos Maven ou Gradle:

    Selecione a versão desejada do Spring Boot:

    Digite o nome do pacote (podemos definir um valor padrão):

    Digite o nome do projeto (podemos definir um valor padrão):

    E selecione todas as dependências necessárias. Não se preocupe se você esquecer alguma dependência. Podemos modificá-la a qualquer momento:

    Painel de Primavera

    Para executar ou depurar um projeto Spring, temos algumas opções diferentes:

    • 1: Use o Spring Dashboard e clique no ícone Play para iniciar o projeto.
    • 2: Abra a classe principal e clique em Executar ou Depurar .
    • 3: Clique no ícone Play que obtemos ao abrir o projeto no VSCode.

    O painel é muito útil e fornece informações significativas:

    • 1: Ele lista todos os Beans criados e, em cada classe, você também pode verificar quais Beans foram injetados.
    • 2: Acima de cada ponto final nos Controladores, será adicionado um link para que você possa clicar nele e abri-lo no navegador – isso é muito útil para solicitações GET.
    • 3: E também lista todos os endpoints e, se você passar o mouse, também verá um ícone de globo que você pode usar para abrir esse link no navegador.

    Testando APIs REST

    Quando se trata de testar os endpoints do nosso projeto, podemos obter um recurso semelhante ao de outros IDEs instalando a extensão REST Client .

    Esta extensão nos permite criar um arquivo .http e adicionar solicitações de amostra à nossa API (semelhante ao Postman). Por exemplo, eu gosto de criar um arquivo api.httpcom as solicitações para testar meus endpoints:

    Você pode enviar este arquivo para o repositório e todos que trabalham no projeto podem usá-lo localmente, desde que tenham a extensão instalada.

    Modificando pom.xml – Spring starters

    Um dos meus recursos favoritos do pacote de extensão Spring é poder adicionar dependências ao pom.xml diretamente do VSCode.

    Se você abrir o pom/xmlarquivo e navegar até as dependências, verá um link que permitirá adicionar novos pacotes iniciais ao projeto:

    A lista de dependências será aberta e você poderá selecionar dependências adicionais ou remover as existentes. Você também pode modificar as dependências clicando com o botão direito do mouse no pom.xmlarquivo e selecionando Adicionar Iniciadores… . Uma terceira alternativa é usar a Paleta de Comandos.

    Você receberá uma notificação solicitando confirmação para adicionar a nova dependência.

    Neste exemplo, selecionamos o suporte ao Docker Compose. Se você selecionar essa dependência ao criar um novo projeto Spring, ele também adicionará um docker.composearquivo. No entanto, essa opção não adiciona nem modifica arquivos adicionais, apenas atualiza o pom.xmlarquivo.

    Projetos Fullstack

    Para projetos full-stack em que você precisa lidar com uma API Spring (ou vários serviços!) e um front-end, o VSCode se destaca. Ele elimina o incômodo de memorizar atalhos diferentes entre IDEs. Você pode alternar facilmente entre projetos, mantendo todos os serviços relevantes abertos simultaneamente:

    Você ainda terá o mesmo suporte e, além disso, poderá ter suas extensões de front-end instaladas.

    Se preferir ter janelas diferentes abertas (uma para o front-end e outra para o back-end), você pode usar Perfis diferentes que habilitarão apenas as extensões necessárias:

    Também gosto de usar a extensão Peacock e definir cores diferentes para poder diferenciar facilmente em qual projeto estou trabalhando.

    Suporte a microsserviços

    O pacote de extensões Spring do VSCode realmente se destaca ao trabalhar com arquiteturas de microsserviços. Se você trabalha frequentemente com vários serviços, vai gostar da facilidade e rapidez que ele traz ao seu fluxo de trabalho – foi isso que me conquistou para o desenvolvimento em Java.

    Adicionar novos serviços a uma solução de microsserviços existente torna-se incrivelmente simplificado. O processo reflete a simplicidade e a velocidade de usar o Spring Initializr diretamente.

    O Spring Dashboard torna muito simples lidar com vários microsserviços:

    Docker

    Não podemos abordar o desenvolvimento moderno sem abordar o Docker. E, claro, também existe uma extensão do Docker para isso.

    Podemos gerenciar imagens, enviá-las para o registro, verificar imagens e logs de contêineres e muito mais. Gosto dessa visualização porque não preciso sair do editor para verificar se uma imagem está sendo executada com sucesso — tudo o que preciso fazer é lembrar de iniciar o serviço Docker!

    Outro recurso interessante é o suporte ao Docker-Compose. Gosto desse recurso premium em outros IDEs, e é ótimo podermos obter suporte semelhante gratuitamente no VSCode. Mas, em vez de abrir o arquivo e clicar no ícone para fazer um compose up, precisamos clicar com o botão direito do mouse no docker.composearquivo e selecionar as opções abaixo:

    Testes e Cobertura de Testes

    Ao instalar a extensão Test Runner for Java ou a extensão Java Platform Support, obtemos suporte a testes para projetos Java. Temos acesso a uma Visualização de Testes, onde podemos ver todos os casos de teste que escrevemos para o projeto, sejam testes unitários ou de integração.

    O que eu gosto nesse suporte é que podemos executar facilmente todos os casos de teste clicando no ícone “Play” . Podemos verificar os logs para ver se há casos de teste com falha. Há também um ícone onde podemos ir diretamente para o código-fonte do teste.

    Quando abrimos um arquivo de teste, também podemos executar todos os casos de teste presentes naquela classe ou executar cada caso de teste individualmente (isso é muito útil se estiver usando TDD – Test Driven Development ).

    Outras extensões úteis

    Estas são as extensões mais úteis que utilizo ao trabalhar com projetos Java. Se você quiser usar as mesmas extensões, criei um pacote de extensões com todas as que considero úteis para desenvolvimento Java + Spring:

    📦 Pacote de extensão Java + Spring

    Vamos verificar algumas extensões adicionais que também são muito úteis.

    Snyk, Checkmarx e Sonarlint

    Manter nosso código seguro e as dependências atualizadas faz parte da manutenção básica que precisamos fazer em nossos projetos. Abaixo está a lista de plugins recomendados e suas descrições:

    • Segurança Snyk : o plugin Snyk Visual Studio Code escaneia e analisa seu código, incluindo dependências de código aberto e configurações de infraestrutura como código. Baixe o plugin a qualquer momento gratuitamente e use-o com qualquer conta Snyk. Escaneie seu código no início do ciclo de desenvolvimento para ajudá-lo a passar nas revisões de segurança e evitar correções dispendiosas posteriormente.

    • Checkmarx : A Checkmarx continua a liderar a abordagem “shift-left” para AppSec, trazendo nossas poderosas ferramentas de AppSec para o seu IDE. Isso permite que os desenvolvedores identifiquem vulnerabilidades e as remediem enquanto codificam. O plugin Checkmarx Visual Studio Code integra-se perfeitamente ao seu IDE, identificando vulnerabilidades em seu código proprietário, dependências de código aberto e arquivos IaC. O plugin oferece insights de remediação acionáveis ​​em tempo real.
    • SonarLint : O SonarLint da Sonar é uma extensão de IDE gratuita que permite corrigir problemas de codificação antes que eles existam. Mais do que um linter, o SonarLint detecta e destaca problemas que podem levar a bugs, vulnerabilidades e code smells enquanto você cria seu código. Ele oferece orientações claras de correção e ajuda educacional, para que você possa corrigir problemas antes que o código seja enviado. Pronto para uso, o SonarLint no VS Code suporta análise de código JS/TS, Python, PHP, Java, C, C++, C#, Go e IaC localmente no seu IDE.

    • Checkstyle para Java : verifique o formato do seu código Java e corrija-o! (muito semelhante ao conhecido plugin Eclipse).

    MongoDB

    O MongoDB para VS Code facilita o trabalho com seus dados no MongoDB diretamente do seu ambiente VS Code. O MongoDB para VS Code é o companheiro perfeito para o MongoDB Atlas, mas você também pode usá-lo com suas instâncias autogerenciadas do MongoDB.

    MySQL

    MySQL Shell para VS Code : Esta extensão permite a edição e execução interativa de SQL para bancos de dados MySQL e o serviço MySQL HeatWave. Ela integra o MySQL Shell diretamente aos fluxos de trabalho de desenvolvimento do VS Code.

    Resumo

    O VSCode oferece uma vasta biblioteca de extensões para personalizar seu ambiente de desenvolvimento, incluindo excelente suporte para provedores de nuvem. Seu robusto conjunto de recursos gratuitos e de alta qualidade e sua interface intuitiva o tornam uma escolha atraente. A transição para o VSCode é surpreendentemente tranquila!

    Se você ainda não usa, recomendo fortemente que experimente. Se descobrir alguma extensão essencial, compartilhe nos comentários – estou sempre ansioso para explorar novas ferramentas.

    Boa codificação!

  • Onde usar ANY no TypeScript

    Onde usar ANY no TypeScript

    Quem me acompanha sabe que eu sou extremamente contra any no código. Principalmente porque o any se tornou uma válvula de escape, sempre que queremos desligar o TypeScript podemos só usar any no nosso código e tudo se resolve. Muita gente já me perguntou: “Ué, então por que o TypeScript tem any se não pode usar?”

    A realidade é que o any é um tipo extremamente importante. Primeiro porque ele é o único tipo que pode ser associado a qualquer tipo sem que esse tipo seja restringido a um tipo mais específico, além disso ele é o tipo mais aberto de todos e aceita todas as coisas, ou seja, o any está em todo lugar.

    Enquanto na maioria dos casos é muito ruim usar any, existem alguns casos interessantes onde, na verdade, o any é a nossa única opção correta.

    Permitindo inferência de tipos

    O primeiro exemplo é justamente o que eu comentei ali em cima. Quando usamos any, não estamos fixando um tipo específico e estamos permitindo que o TS faça a inferência desse tipo. O jeito que o TypeScript resolve tipagens é sempre da mais aberta para a mais fechada, ou seja, o any é como inicializar uma variável numérica como “infinito”.

    Um exemplo clássico disso (que você vai encontrar em muitos lugares) é o ReturnType, um utility type que existe no TypeScript nativamente e basicamente o que ele faz é pegar o tipo do retorno de uma função. Vamos tentar recriar esse tipo:

    type ReturnType<T extends (...args: unknown[]) => unknown> = T extends (...args: unknown[]) => infer Retorno ? Retorno : never

    Basicamente, estamos dizendo que queremos um tipo, esse tipo leva um genérico que vai ser uma função, a gente realmente não se importa com o que existe nos parâmetros ou no retorno da função, então para não ter que usar any vamos usar unknown. Dessa forma nosso tipo fica mais seguro, certo? Mas e se a gente fizer isso aqui:

    const foo = (i: string) => i
    type retorno = ReturnType<typeof foo>
    Vamos ter um erro interessante:
    ype '(i: string) => string' does not satisfy the constraint '(...args: unknown[]) => unknown'.
      Types of parameters 'i' and 'args' are incompatible.
        Type 'unknown' is not assignable to type 'string'.

    Isso porque quando usamos unknown estamos automaticamente dizendo para o TypeScript que a gente não sabe o que tem ali, então o TS vai forçar que a gente faça um casting disso manualmente, o que a gente quer é que o TS faça a inferência sozinho. Então para isso temos que dizer que “não nos importamos com o que está ali” e o TS vai sempre tentar trazer o tipo mais específico possível.

    Se mudarmos a nossa declaração de tipo para:

    type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer Retorno ? Retorno : never

    Nosso erro some e o nosso tipo retorno vai ser string.

    Valores externos

    Uma outra opção é quando estamos tratando com valores que são verdadeiramente externos. Esse é um valor válido para usar any, mas sempre temos que lembrar que é necessário que a gente faça a tipagem desses tipos depois. Por exemplo:

    const dadoExterno: any = algumaChamadaDeAPI()
    // processamento aqui
    const dadoInterno: SeuTipo = dadoConfirmado
    O uso do any nesse caso é somente quando a gente está obtendo o dado, através de um JSON.parse ou qualquer outra chamada, mas é extremamente importante que a gente não mantenha o any depois, se tivermos que fazer qualquer processamento, é importante que a gente faça o casting desse tipo para outro tipo bem mais específico.

    Migração de base

    Um dos casos de uso mais importantes é quando estamos fazendo a migração de JavaScript para TypeScript em uma base de código antiga. Para isso é comum que a gente comece fazendo a migração usando any no código antigo e, aos poucos, vai passando esses any para tipos específicos.

    Esse é provavelmente o caso mais ok para o uso de any em qualquer aplicação.

  • Novidades do .NET 10: melhorias na ordenação numérica de strings

    Novidades do .NET 10: melhorias na ordenação numérica de strings

    Novidades do .NET 10 | Neste novo artigo abordo uma melhoria envolvendo a ordenação de strings com conteúdo numérico, através de uma nova opção que foi incorporada ao enum CompareOptions  (namespace System.Globalization): trata-se de CompareOptions.NumericOrdering, alternativa que pode ser utilizada em conjunto com a classe StringComparer (namespace System) em instruções que envolvam ordenação e comparação de valores numéricos em strings.

    Podemos observar o uso de CompareOptions.NumericOrdering com StringComparer na listagem a seguir:

    • Uma instância de StringComparer é gerada, recebendo como um dos parâmetros o valor CompareOptions.NumericOrdering (linhas 13 e 14);
    • Ao acionarmos o método Equals nesta referência de StringComparer (linha 25) conseguimos determinar que 007 e 7 representam um mesmo valor, ao passo em que não há igualdade entre 30.49 e 30.489;
    • Este tipo de comportamento também estará disponível para a utilização com o método Order em um array de strings (linha 34), com a correta ordenação de diferentes valores em texto que representam números de 6 a 10;
    • E também com o método Contains, em um exemplo baseado no uso de uma instância do tipo HashSet (linha 41). Uma análise envolvendo a pesquisa do texto 10 identificará que o mesmo corresponde a 0010, um dos itens que compõem a instância mencionada.
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.Text.Json;
    
    Console.WriteLine("***** Testes com .NET 10 | Numeric Ordering for String Comparison *****");
    Console.WriteLine($"Versao do .NET em uso: {RuntimeInformation
        .FrameworkDescription} - Ambiente: {Environment.MachineName} - Kernel: {Environment
        .OSVersion.VersionString}");
    
    Console.WriteLine();
    Console.WriteLine("*** Testando Equals() ***");
    
    StringComparer numericStringComparer = StringComparer.Create(
        CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
    
    var numbersEquality = new (string value1, string value2)[]
    {
        ("007", "7"),
        ("8", "8"),
        ("020.5", "20.5"),
        ("30.49", "30.489")
    };
    foreach (var numbers in numbersEquality)
        Console.WriteLine($"{numbers.value1} == {numbers.value2} ? " +
            numericStringComparer.Equals(numbers.value1, numbers.value2));
    
    Console.WriteLine();
    Console.WriteLine("*** Testando Order() ***");
    
    var dotnetVersions = new[] { "10.0", "7.0", "9", "8.0", "6" };
    Console.WriteLine($"{nameof(dotnetVersions)} = {JsonSerializer.Serialize(dotnetVersions)}");
    
    var dotnetVersionsOrdered = dotnetVersions.Order(numericStringComparer);
    Console.WriteLine($"{nameof(dotnetVersionsOrdered)} = {JsonSerializer.Serialize(dotnetVersionsOrdered)}");
    
    Console.WriteLine();
    Console.WriteLine("*** Testando Contains() ***");
    
    var csharpVersions = new HashSet<string>(numericStringComparer) { "08", "009", "0010", "011", "12", "13" };
    for (int v = 7; v <= 14; v++)
        Console.WriteLine($"A versao {v} do C# esta na lista? {csharpVersions.Contains($"{v}")}");

    O resultado da execução desta aplicação de testes pode ser observado na imagem seguinte:

    Press enter or click to view image in full size

    Esse exemplo foi disponibilizado em um repositório do GitHub:

    https://github.com/renatogroffe/DotNet10-ConsoleApp-Numeric-Ordering-for-String-Comparison

    Caso achem útil esta solução, peço por favor um ⭐️ no repositório apoiando. Fica também o convite para que vocês me sigam lá no GitHub!