Advertisement · 728 × 90
#
Hashtag
#BrazilianDevs
Advertisement · 728 × 90
Post image

Pokémon Sword & Shield on GBA?! Omega Hack v1.2 Is Here – Full Galar Region on Retro Hardware!

#PokemonOmega, #GBAHack, #PokemonSwordAndShield, #ROMHack, #FanGame, #GalarOnGBA, #MegaEvolution, #Dynamax, #IsleOfArmor, #CrownTundra, #PCLG, #BrazilianDevs, #RetroGaming,

0 0 0 0
Post image

Lidando com Concorrência em Java – Lock Pessimista Partindo do pressuposto que concorrências acontecerão em uma aplicação multithread, nesse post vou descrever de forma simples e… The post...

#Software #braziliandevs #java #jpa #prodsens #live

Origin | Interest | Match

2 0 0 0
Post image

Lidando com Concorrência em Java – Lock Pessimista Partindo do pressuposto que concorrências acontecerão em uma aplicação multithread, nesse post vou descrever de forma simples e… The post...

#Software #braziliandevs #java #jpa #prodsens #live

Origin | Interest | Match

0 0 0 0
Preview
Brighter V10: Utilizando PostgreSQL como Gateway de Mensagens Leve Em artigos anteriores, abordei o Brighter V10 RC1. Uma adição notável é o suporte ao Postgres como...

Brighter V10: Utilizando PostgreSQL como Gateway de Mensagens Leve Em artigos anteriores, abordei o Brighter V10 RC1 . Uma adição notável é o suporte ao Postgres como gateway de mensagens, idea...

#braziliandevs #dotnet #brighter #postgres

Origin | Interest | Match

0 0 0 0
Preview
Sonhar em trabalhar por conta própria não é nada fácil. Sonhar em trabalhar por conta própria não é nada fácil. Pois estamos sempre sentindo: * Ansiedade de não saber por onde começar. * Sobrecarga de informações. * Estresse de tentar dar conta de tudo. Algo que já me ajudou algumas vezes foi imaginar que 80% dos resultados que busco virão de apenas 20% dos meus esforços. Ao eliminar o que não é essencial, você permite que o que realmente importa receba mais atenção.
0 0 0 0
Preview
Mensageria Transacional no .NET: Integrando o Padrão Outbox do Brighter com SQL Server e RabbitMQ ## Introdução No artigo anterior, abordamos os conceitos básicos do padrão Outbox, que é uma estratégia usada em sistemas distribuídos para garantir que operações de banco de dados e mensageria sejam tratadas de forma consistente. Vimos como ele evita inconsistências causadas por falhas durante a publicação de eventos ou comandos, armazenando as mensagens em uma tabela de outbox antes de enviá-las ao broker de mensagens (ex: RabbitMQ). ## Projeto A ideia principal é enviar um comando para **criar um pedido** (`CreateNewOrder`). Ao criar o pedido, serão publicados dois eventos: `OrderPlaced` e `OrderPaid`. Em caso de falha, nenhuma mensagem deve ser enviada. ### Requisitos * **.NET 8+** * **Podman** (ou Docker) para executar containers locais: * **SQL Server** * **RabbitMQ** * Conhecimento prévio sobre Brighter e RabbitMQ. * Pacotes NuGet necessários: * Paramore.Brighter.Extensions.DependencyInjection * Paramore.Brighter.Extensions.Hosting * Paramore.Brighter.MessagingGateway.RMQ * Paramore.Brighter.Outbox.MsSql * Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection * Paramore.Brighter.ServiceActivator.Extensions.Hosting ## Mensagens Para este projeto, precisaremos das seguintes mensagens: * `CreateNewOrder` (comando) * `OrderPlaced` (evento) * `OrderPaid` (evento) public class CreateNewOrder : Command(Guid.NewGuid()) { public decimal Value { get; set; } } public class OrderPlaced : Event(Guid.NewGuid()) { public string OrderId { get; set; } = string.Empty; public decimal Value { get; set; } } public class OrderPaid : Event(Guid.NewGuid()) { public string OrderId { get; set; } = string.Empty; } ## Mapeadores de Mensagem Como apenas os eventos `OrderPlaced` e `OrderPaid` são publicados no RabbitMQ, implementamos mapeadores usando serialização JSON: public class OrderPlacedMapper : IAmAMessageMapper<OrderPlaced> { public Message MapToMessage(OrderPlaced request) { var header = new MessageHeader { Id = request.Id, TimeStamp = DateTime.UtcNow, Topic = "order-placed", MessageType = MessageType.MT_EVENT }; var body = new MessageBody(JsonSerializer.Serialize(request)); return new Message(header, body); } public OrderPlaced MapToRequest(Message message) { return JsonSerializer.Deserialize<OrderPlaced>(message.Body.Bytes)!; } } public class OrderPaidMapper : IAmAMessageMapper<OrderPaid> { public Message MapToMessage(OrderPaid request) { var header = new MessageHeader { Id = request.Id, TimeStamp = DateTime.UtcNow, Topic = "order-paid", MessageType = MessageType.MT_EVENT }; var body = new MessageBody(JsonSerializer.Serialize(request)); return new Message(header, body); } public OrderPaid MapToRequest(Message message) { return JsonSerializer.Deserialize<OrderPaid>(message.Body.Bytes)!; } } ## Manipuladores de Requisições Vamos registrar logs para os eventos `OrderPlaced` e `OrderPaid`. public class OrderPlaceHandler(ILogger<OrderPlaceHandler> logger) : RequestHandlerAsync<OrderPlaced> { public override Task<OrderPlaced> HandleAsync(OrderPlaced command, CancellationToken cancellationToken = default) { logger.LogInformation("{OrderId} foi registrado com valor {OrderValue}", command.OrderId, command.Value); return base.HandleAsync(command, cancellationToken); } } public class OrderPaidHandler(ILogger<OrderPaidHandler> logger) : RequestHandlerAsync<OrderPaid> { public override Task<OrderPaid> HandleAsync(OrderPaid command, CancellationToken cancellationToken = default) { logger.LogInformation("{OrderId} foi pago", command.OrderId); return base.HandleAsync(command, cancellationToken); } } ### Criação do Pedido O manipulador `CreateNewOrderHandler` vai esperar 10ms para simular um processo, publicar `OrderPlaced`, verificar se o valor é divisível por 3 (simulando uma regra de negócio), e publicar `OrderPaid` somente em casos válidos. public class CreateNewOrderHandler( IAmACommandProcessor commandProcessor, IUnitOfWork unitOfWork, ILogger<CreateNewOrderHandler> logger) : RequestHandlerAsync<CreateNewOrder> { public override async Task<CreateNewOrder> HandleAsync(CreateNewOrder command, CancellationToken cancellationToken = default) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { string id = Guid.NewGuid().ToString(); logger.LogInformation("Criando novo pedido: {OrderId}", id); await Task.Delay(10, cancellationToken); // Simula um processo _ = await commandProcessor.DepositPostAsync(new OrderPlaced { OrderId = id, Value = command.Value }, cancellationToken: cancellationToken); if (command.Value % 3 == 0) { throw new InvalidOperationException("valor inválido"); } _ = await commandProcessor.DepositPostAsync(new OrderPaid { OrderId = id }, cancellationToken: cancellationToken); await unitOfWork.CommitAsync(cancellationToken); return await base.HandleAsync(command, cancellationToken); } catch { logger.LogError("Dados inválidos"); await unitOfWork.RollbackAsync(cancellationToken); throw; } } } #### Entendendo o Funcionamento * O `IUnitOfWork` compartilha a transação SQL do Brighter, garantindo atomicidade (persistência do pedido + gravação no outbox). * Os eventos só são publicados se a transação for confirmada. ## Configuração do Microsoft SQL Server Para integrar o padrão Outbox com o SQL Server, primeiro crie a tabela `OutboxMessages`. ### 1. Esquema da Tabela SQL IF OBJECT_ID('OutboxMessages', 'U') IS NULL BEGIN CREATE TABLE [OutboxMessages] ( [Id] [BIGINT] NOT NULL IDENTITY, [MessageId] UNIQUEIDENTIFIER NOT NULL, [Topic] NVARCHAR(255) NULL, [MessageType] NVARCHAR(32) NULL, [Timestamp] DATETIME NULL, [CorrelationId] UNIQUEIDENTIFIER NULL, [ReplyTo] NVARCHAR(255) NULL, [ContentType] NVARCHAR(128) NULL, [Dispatched] DATETIME NULL, [HeaderBag] NTEXT NULL, [Body] NTEXT NULL, PRIMARY KEY ([Id]) ); END ### Registro de Dependências Registre o _outbox_ e a conexão transacional no contêiner de injeção de dependência: services .AddServiceActivator(opt => { /* Configuração de inscrição */ }) .UseMsSqlOutbox(new MsSqlConfiguration(ConnectionString, "OutboxMessages"), typeof(SqlConnectionProvider)) .UseMsSqlTransactionConnectionProvider(typeof(SqlConnectionProvider)) .UseOutboxSweeper(opt => opt.BatchSize = 10); #### Explicação Técnica * `UseMsSqlOutbox`: Associa o _outbox_ ao SQL Server. * `UseOutboxSweeper`: Configura a verificação periódica de mensagens não entregues. ### Gerenciamento de Transações Para garantir atomicidade entre lógica de negócio e publicação de mensagens no Brighter, implementamos `IMsSqlTransactionConnectionProvider` e `IUnitOfWork`, compartilhando o mesmo contexto transacional. #### a. SqlConnectionProvider public class SqlConnectionProvider(SqlUnitOfWork sqlConnection) : IMsSqlTransactionConnectionProvider { private readonly SqlUnitOfWork _sqlConnection = sqlConnection; public SqlConnection GetConnection() { return _sqlConnection.Connection; } public Task<SqlConnection> GetConnectionAsync(CancellationToken cancellationToken = default) { return Task.FromResult(_sqlConnection.Connection); } public SqlTransaction? GetTransaction() { return _sqlConnection.Transaction; } public bool HasOpenTransaction => _sqlConnection.Transaction != null; public bool IsSharedConnection => true; } #### b. Interface `IUnitOfWork` public interface IUnitOfWork { Task BeginTransactionAsync(CancellationToken cancellationToken, IsolationLevel isolationLevel = IsolationLevel.Serializable); Task CommitAsync(CancellationToken cancellationToken); Task RollbackAsync(CancellationToken cancellationToken); } #### c. Implementação do `SqlUnitOfWork` public class SqlUnitOfWork(MsSqlConfiguration configuration) : IUnitOfWork { public SqlConnection Connection { get; } = new(configuration.ConnectionString); public SqlTransaction? Transaction { get; private set; } public async Task BeginTransactionAsync(CancellationToken cancellationToken, IsolationLevel isolationLevel = IsolationLevel.Serializable) { if (Transaction == null) { if (Connection.State != ConnectionState.Open) { await Connection.OpenAsync(cancellationToken); } Transaction = Connection.BeginTransaction(isolationLevel); } } public async Task CommitAsync(CancellationToken cancellationToken) { if (Transaction != null) { await Transaction.CommitAsync(cancellationToken); } } public async Task RollbackAsync(CancellationToken cancellationToken) { if (Transaction != null) { await Transaction.RollbackAsync(cancellationToken); } } public async Task<SqlCommand> CreateSqlCommandAsync(string sql, SqlParameter[] parameters, CancellationToken cancellationToken) { if (Connection.State != ConnectionState.Open) { await Connection.OpenAsync(cancellationToken); } SqlCommand command = Connection.CreateCommand(); if (Transaction != null) { command.Transaction = Transaction; } command.CommandText = sql; if (parameters.Length > 0) { command.Parameters.AddRange(parameters); } return command; } } #### d. Registro no DI services .AddScoped<SqlUnitOfWork, SqlUnitOfWork>() .TryAddScoped<IUnitOfWork>(provider => provider.GetRequiredService<SqlUnitOfWork>()); ## Conclusão Ao implementar o padrão **Outbox** com o **Brighter** e **SQL Server** , demonstramos como garantir consistência transacional entre atualizações no banco de dados e publicação de mensagens. Este exemplo mostrou que: 1. **Mensagens só são publicadas se a transação for bem-sucedida** : * Usando `DepositPostAsync`, mensagens como `OrderPlaced` e `OrderPaid` são armazenadas na tabela `OutboxMessages` dentro da mesma transação dos dados de negócio. * Se o manipulador falhar (ex: erro simulado), a transação é revertida e nenhuma mensagem órfã é enviada. * O `IMsSqlTransactionConnectionProvider` garante que atualizações no banco e registros no _outbox_ compartilhem a mesma transação. 2. **Tolerância a Falhas via Sweeper do Outbox** : * O `UseOutboxSweeper` verifica periodicamente por mensagens não entregues e tenta reenviá-las até que o RabbitMQ confirme o recebimento. * Isso desacopla a publicação de mensagens da execução do manipulador, garantindo confiabilidade mesmo em falhas temporárias do broker. 3. **Arquitetura Desacoplada** : * A aplicação se concentra em transações locais. * O Brighter trata a entrega de mensagens assincronamente, simplificando a escalabilidade e evitando acoplamento com a infraestrutura de mensageria. Esta abordagem mostra como o **Brighter abstrai complexidade** , permitindo que desenvolvedores foquem na lógica de negócio enquanto garante confiabilidade em sistemas distribuídos. Para uso em produção, combine esse padrão com: * **Ferramentas de monitoramento** (ex: Prometheus) * **Filas de Cartas Mortas (DLQ)** para tratar mensagens problemáticas * **Índices na tabela de outbox** nas colunas `Dispatched` e `Timestamp` para melhorar performance. ## Referências * Código completo no GitHub
0 0 0 0
Preview
Educação, Developer Relations e Inteligência Artificial Eu acredito muito no poder da educação. Principalmente em situações como a que estamos vivenciando agora, com a explosão de novidades sobre Inteligência Artificial e os medos que surgem com tudo o que tem sido falado por aí. Na minha opinião, as ferramentas de IA são muito poderosas para serem colocadas na mão das pessoas sem quaisquer orientações. O uso equivocado, as crenças ao redor disso, o fato de acreditar ou não nas informações geradas sem o senso crítico de quando há alienação e quando há, de fato, respostas válidas, o medo de perder o emprego por conta dessa tecnologia e muitos outros fatores, podem ser muito complicados sem educação. A área de Developer Relations pode ser uma forte aliada nesse processo de educação nas empresas. Como Developer Advocate de uma plataforma de IA, vejo o impacto do meu trabalho acontecendo diariamente, levando o conhecimento de forma leve e prática, mostrando como as coisas funcionam de uma maneira mais profunda, dando na mão das pessoas de diversas áreas, e não apenas tecnologia, ferramentas que serão um apoio para seus desafios, tirando o peso do que as assusta e incluindo a leveza de resolver seus problemas diários. Investir na área de Developer Relations pode ser fundamental por diversos motivos. E educação é uma das chaves que pode abrir várias portas de soluções dentro das empresas. Quem investe em educação, investe no capital intelectual. E quem investe no capital intelectual, investe em inovação.
0 0 0 0
Preview
Desempenho - ZLinq Olá! Este é mais um post da seção **Desempenho** e, desta vez, vamos tratar de uma ferramenta muito útil que nos auxilia a lidar com `Span<T>`: O ZLinq. Vamos lá! ## Motivação Como sabemos, não é possível escrever expressões Linq sobre spans e, por isso, é comum termos de iterar em coleções acessadas por esse tipo para realizamos operações. O ZLinq ambiciona mudar esse comportamento! Por meio de um método que tranforma seu `Span<T>` em um `ValueEnumerable` e permite que, em sua superfície, sejam executadas expressões como aquelas do Linq -- digo como aquelas do Linq e não as deste porque as expressões são todas implementadas pela própria biblioteca, que alega ter 99% das implementações do Linq (como os métodos `Select`, `Where` e `Aggregate` por exemplo). Aqui temos um exemplo do benchmarking realizado para fins de teste desta biblioteca: [MemoryDiagnoser] public class ZLinqBenchmark { [Benchmark] public int Linq() { DummyAccount[] dummies = new DummyAccount[1_000]; for(int i = 0; i < dummies.Length; i++) dummies[i] = new (Guid.NewGuid(), i * 100); return dummies.Where(d => d.Balance > 1_000) .Select(d => d.AccountId) .Count(); } [Benchmark] public int ZLinq() { Span<DummyAccount> dummies = stackalloc DummyAccount[1_000]; for (int i = 0; i < dummies.Length; i++) dummies[i] = new(Guid.NewGuid(), i * 100m); return dummies.AsValueEnumerable() .Where(d => d.Balance > 1_000) .Select(d => d.AccountId) .Count(); } } Como podemos observar, não há grandes diferenças entre os usos do Linq e Zlinq, na verdade há apenas duas: o método `Linq` utiliza um array, enquanto o `ZLinq` utiliza um `Span<T>` e; o método `ZLinq` lança mão do `ValueEnumerable`. Essas simples diferenças produziram o seguinte resultado no benchmarking: Repare nas alocações: é uma economia de 32k por execução. Agora imagine isso no caminho crítico de uma aplicação, como oferece um desempenho superior por conta da necessidade reduzida de acionamentos do _Garbage Collector_. Impressionante! Algo interessante aqui: em cenários onde os dois testes utilizem IEnumerable com tipos complexos, e o ZLinq não utilize um `Span<T>`, alocações passam a ocorrer, como aquelas que ocorrem com o Linq. Veja: Além da alocação próxima, o tempo de execução foi um pouco maior. Ter esse conhecimento é interessante para evitar o uso indiscriminado da biblioteca. O ideal é utilizá-la quando há um cenário propício, no caso quando se usa `Span<T>`. ## Conclusão ZLinq é uma ferramenta que entrega um desempenho ótimo ao lidarmos com `Span<T>` tendo a necessidade de utilizar expressões Linq. A biblioteca ainda está em começo de vida, conta com pouco mais de 20k downloads, mas é mantida pelo mesmo criador do MessagePack-CSharp sobre o qual já falamos por aqui. Vale a pena esperar por mais funcionalidades e melhorias com o tempo. O código do benckmark está, como sempre, disponível no Github. Divirta-se e me diga o que achou. Gostou do post? Me deixe saber pelos indicadores. Tem dúvidas ou sugestões? Deixe um comentário ou me procure em minhas redes. Muito obrigado por ler até aqui, e até o próximo post.
0 0 0 0
Preview
Como o protocolo HTTP funciona? Sempre que você acessa um site pode notar que a URL exibida no navegador é algo como `https://dev.to`, você sabe que `dev.to` é o endereço do site, a localização dele na web. Mas o que são as letrinhas _HTTPS_ ou _HTTP_ antes do endereço? Neste artigo vamos entender o que é o HTTP, para quê serve e como funciona. ## O que é HTTP O Hyper Transfer Text Protocol, ou HTPP, é um protocolo de transferência de documentos de hipermídia da camada de aplicação. Ele foi projeto para comunicação entre clientes e servidores, mas também pode ser usado para outros fins, como comunicação máquina a máquina, acesso programático a APIs e muito mais. Ele é enviado por TCP ou por conexção TCP criptografada por TLS ou SSL, o HTTPS utiliza esta camada adicional de segurança. Confira mais detalhes da história do HTTP. ## Aspectos do HTTP O HTTP segue o modelo cliente-servidor, no qual clientes (na maioria das vezes um navegador web) e servidores se comunicam por meio de mensagens individuais. As mensagens enviadas pelo cliente, as solicitações ou requisições, são chamadas de _requests_ e as mensagens enviadas como resposta pelo servidor são chamadas _responses_. O HTTP é _stateless_ , isso significa que ele não guarda qualquer dado entre requisições, cada requisição do cliente é tratada de forma independente. Porém ele não é _sesionless_ , a adição de cookies HTTP permitem que os aplicações web armazenem quantidades limitadas de dados e lembrem informações de estado. ## Como funciona o HTTP O cliente, um navegador web geralmente, faz uma requisição a um servidor para obter um recurso como uma página HTML, imagens ou arquivos. Neste momento é aberta uma conexão ente os dois. Na requisição HTTP é explicitado o método pretendido e o caminho do recurso desejado. O servidor recebe essa solicitação, busca o recurso e ao encontrar responde ao navegador uma mensagem contendo o recurso e informações adicionais no cabeçalho da mensagem HTTP. O cliente recebe a resposta do servior, e encerra a conexão. ## Mensagens HTTP As mensagens HTTP, conforme definido em HTTP/1.1 e anteriores, são legíveis por humanos. No HTTP/2, essas mensagens são incorporadas em uma estrutura binária, um quadro, permitindo otimizações como compactação de cabeçalhos e multiplexação. ### Requisições Exemplo de requisição HTTP: GET / HTTP/1.1 Host: meusite.com Accept-Language: en-US * `GET` é o método HTTP. * `/` é o caminho do recurso a ser buscado. * `HTTP/1.1` a versão do protocolo. * O `Host`e `Accept-Language` são parte do cabeçalho HTTP e fornecem informações adicionais sobre navegador e preferências de conteúdo. * Ainda pode conter um corpo para alguns métodos como o `POST`, que contém o recurso enviado. ### Respostas Exemplo de resposta: HTTP/1.1 200 OK Date: Wed, 09 MAY 2025 01:58:00 GMT Content-Length: 1999 Content-Type: text/html; charset=UTF-8 * `HTTP/1.1` a versão do protocolo HTTP que eles seguem. * `200` um código de status, indicando que a solicitação foi bem-sucedida. * `OK` uma mensagem de status, uma breve descrição não autorizada do código de status. * Cabeçalhos HTTP, como aqueles para solicitações. * Opcionalmente, um corpo que contém o recurso buscado. ## Métodos HTTP O HTTP define um conjunto de certos métodos de solicitação para enviar mensagens indicando a finalidade da solicitação e o que é esperado em caso de sucesso. ### GET Este método tem finalidade de solicitar um recurso especifico. É usado para recuperar dados e não deve conter conteúdo no corpo da solicitação. ### POST Este método indica que a solicitação faz a publicação de um recurso no servidor. ### PUT Substitui todas as entidades do recurso alvo pelo conteúdo do corpo da solicitação. ### PATCH Faz modificações parciais em um recurso, que ao contrário do `PUT` substituí um recurso por inteiro. ### DELETE Este método excluí o recurso. ### CONNECT Estabele um túnel para o servidor identificado pelo recurso alvo. ### OPTIONS Descreve opções de comunição para o recurso alvo. ## Conclusão E então chegamos ao fim deste texto, espero que eu tenha conseguido explicar minimamente o que é o HTTP e que tenha ajudado a você leitor. Abraços. Referências: * https://developer.mozilla.org/en-US/docs/Web/HTTP * https://www.alura.com.br/artigos/http
0 0 0 0
Preview
Como maximizar o uso da Inteligência Artificial com a engenharia de prompt A inteligência artificial tem se tornado uma aliada valiosa em diversas áreas dentro das empresas. Um dos fatores que pode determinar a eficácia dessa tecnologia é a qualidade dos prompts utilizados para interagir com ela. Neste artigo, discutiremos a importância de construir prompts bem elaborados e forneceremos dicas práticas para garantir que você obtenha as melhores respostas das LLMs. ## O que é Engenharia de Prompt A engenharia de prompt surgiu como uma nova habilidade e ela é tão recente que ainda estamos aprendendo como usá-la. Essa habilidade nos permite criar inputs otimizados para os modelos de linguagem da inteligência artificial nos retornarem respostas que sejam relevantes. É importante ressaltar que a qualidade do prompt pode influenciar significativamente a acurácia da resposta recebida. ## Como estruturar um prompt Considere as seguintes etapas para criar seu prompt: **1. Ações** Comece o prompt com verbos de ações, como “escreva", “analise”, “crie”, entre outros. Deve ser uma pergunta ou solicitação bem definida, tomando o cuidado de evitar ambiguidades. **2. Dê contexto** Esse é um dos pontos mais importantes. Isso inclui informações sobre a situação atual, objetivos, informações sobre o projeto que está trabalhando ou quaisquer limitações que você possa ter para a sua solicitação. **3. Formatação** Forneça orientações sobre como você deseja que a resposta seja formatada. Você espera uma lista? Um e-mail? Um código? Um texto em formato markdown? Esse processo pode facilitar muito o entendimento do retorno da IA. **4. Exemplificação** Dependendo do tipo de solicitação que você está fazendo, pode fazer sentido dar exemplo para IA sobre o que está pedindo. Se está pedindo um e-mail, você pode dar outro e-mail como exemplo do que se espera de resultado. Se está desenvolvendo um código, você pode apontar os arquivos que já estão com o código pronto e que representam o que você espera como retorno. **5. Persona** Outro ponto que pode melhorar o seu prompt seria dizer qual é a persona. Se você está desenvolvendo um código, você pode dizer que a persona da IA é uma pessoa desenvolvedora. Se você está criando histórias a partir de um épico, você pode dizer que a persona da IA é uma pessoa product manager e assim por diante. **6. Entonação** Dependendo da sua solicitação, pode fazer sentido pedir uma entonação específica no seu prompt. Essa entonação pode ser, por exemplo: ou tom de voz casual, ou um tom de voz mais formal, ou entusiasmo, entre outros. ## Vamos continuar construindo prompts A criação de prompts eficazes é uma habilidade importante que pode maximizar o potencial da IA. Ao focar em clareza, contexto e especificidade, qualquer pessoa, independente da função ou área, pode aprimorar suas interações com a IA e, assim, alcançar melhores resultados. Invista tempo na elaboração de seus prompts e observe como isso pode transformar suas experiências com a tecnologia.
0 0 0 0
Preview
Apresentando holo-fn: uma biblioteca funcional mínima para TypeScript The English version of this post is here ## Introdução Você é um desenvolvedor TypeScript que adora programação funcional? Então, eu tenho algo para você: **holo-fn** – uma biblioteca funcional e leve projetada para lidar com valores opcionais, erros e resultados de uma forma simples, segura em tipos e imutável. O nome **holo-fn** é inspirado pelo **Holocron** do universo **Star Wars**. Um Holocron é um dispositivo usado para armazenar grandes quantidades de conhecimento, passadas ao longo das gerações. Da mesma forma, o holo-fn serve como um repositório para poderosos construtos de programação funcional (como `Maybe`, `Either` e `Result`), projetados para tornar seu código TypeScript mais limpo, seguro e componível. ## 🎯 Por que usar o holo-fn? **holo-fn** fornece poderosas monads como `Maybe`, `Either` e `Result`. Esses construtos funcionais ajudam você a escrever um código mais seguro, limpo e componível, lidando com casos como valores ausentes, erros e resultados de maneira mais previsível e gerenciável. Ele foi desenvolvido para funcionar perfeitamente com a função `pipe` do **Rambda** para um estilo ainda mais funcional. It is built to work seamlessly with the `pipe` function from Rambda for an even more functional style. ### Principais Funcionalidades: * **Tipos funcionais:** Maybe, Either e Result para lidar com valores opcionais, erros e resultados. * **Imutabilidade:** Tudo é imutável por padrão, tornando seu código mais previsível e seguro. * **Segurança de Tipo:** Totalmente tipado com TypeScript, para que você possa confiar na análise estática do TypeScript. * **Cobertura de Testes 100%:** Rigorosamente testado com 100% de cobertura, garantindo estabilidade e confiabilidade. * **Sem dependências:** Zero dependências, mantendo seu projeto leve. * **Compatibilidade com Rambda:** Criado para funcionar perfeitamente com a função pipe do Rambda. ## 📦 Instalação Você pode instalar facilmente o holo-fn via npm: npm install holo-fn ## 🚀 Começando Aqui está um exemplo rápido de como começar com o **holo-fn**. https://richecr.github.io/holo-fn/getting_started/ ### Maybe `Maybe` é usado para lidar com valores que podem ser nulos ou indefinidos. Ele previne erros de null/undefined ao envolver os valores em `Just` (para valores presentes) ou `Nothing` (para valores ausentes). import { fromNullable } from 'holo-fn/maybe' const name = fromNullable('Rich') .map(n => n.toUpperCase()) .unwrapOr('Anonymous') console.log(name) // RICH Você pode ver os métodos que o Maybe suporta nesta página. ### Either `Either` representa uma computação que pode ter sucesso (`Right`) ou falhar (`Left`). Isso é especialmente útil para tratamento de erros, onde o lado esquerdo representa um erro e o lado direito representa um sucesso. import { Right } from 'holo-fn/either' const result = new Right(10) .map(n => n * 2) .unwrapOr(0) console.log(result); // 20 Você pode ver os métodos que o Either suporta nesta página. ### Result `Result` é semelhante ao `Either`, mas especificamente projetado para operações que podem ter sucesso com um valor (`Ok`) ou falhar com um erro (`Err`). import { Ok } from 'holo-fn/result' const result = new Ok<number, string>(10) .map(n => n + 1) .unwrapOr(0) console.log(result) // 11 Você pode ver os métodos que o Result suporta nesta página. ## 🛠️ Funções Curried Para um estilo mais funcional, **holo-fn** também oferece versões curried da maioria dos métodos. Você pode facilmente compondo-os usando o `pipe` do **Rambda**. Exemplos: import { Err, mapErrR } from 'holo-fn/result'; const result = pipe( new Err("Error"), mapErrR((e) => `Mapped error: ${e}`), (res) => res.unwrapOr("No value") ); console.log(result); // "No value" import { Left, mapLeftE } from 'holo-fn/either'; const result = pipe( new Left("Error"), mapLeftE((e) => `Mapped error: ${e}`), (res) => res.unwrapOr("No value") ); console.log(result); // "No value" Temos a documentação para essas funções para cada tipo que você deseja usar: * Funções Curried para Maybe * Funções Curried para Either * Funções Curried para Result ## 🧠 Por que escolher o holo-fn? * **Imutabilidade:** Todos os valores são imutáveis por padrão. Isso reduz erros e torna o código mais fácil de entender. * **Composição:** Use `map`, `chain`, e `unwrapOr` para fácil composição de pipelines funcionais. * **Segurança de Tipo:** holo-fn é totalmente tipado com TypeScript, garantindo que seu código seja seguro, sem surpresas. * **Leve:** Foi projetado para ser leve, com zero dependências. * **Compatível com Rambda:** Funciona muito bem com o `pipe` do **Rambda** , permitindo padrões de programação funcional. ## 📈 Considerações de Performance Como o **holo-fn** foi projetado para ser componível e seguro em tipos, também garante que o desempenho não seja comprometido. Os métodos da biblioteca são otimizados para execução rápida e eficiente, com sobrecarga mínima. ## 🤝 Contribua Quer contribuir? Estamos abertos a contribuições! Se você tem familiaridade com TypeScript e programação funcional, fique à vontade para enviar problemas ou pull requests para melhorar a biblioteca. Você pode começar fazendo um fork do repositório e fazendo suas alterações. Depois disso, basta enviar um pull request! Aqui temos uma página dedicada para novos contribuidores e fique a vontade para abrir uma issue de dúvida. ## 🔗 Links * Repositório do GitHub * Página no npm ## Conclusão **holo-fn** é uma biblioteca minimalista, mas poderosa, que torna a programação funcional em TypeScript mais acessível. Com suporte a monads como `Maybe`, `Either` e `Result`, você pode facilmente lidar com casos de borda de forma previsível e limpa. Comece a usá-la em seus projetos TypeScript e aproveite os benefícios da programação funcional!
0 0 0 0
Post image
0 0 0 0
Preview
Dicas de como viver e trabalhar em Portugal como dev Brasileiro Salve meus amigos! Nesse vídeo eu vou compartilhar algumas dicas de como vir a Portugal como dev Brasileiro, aqui eu abordo topicos como: documentos, sálarios, como funciona o mercado de trabalho e mais! Se esse vídeo te ajudou, curti, comenta e compartilha com os nossos amigos dev que querem vir a Europa. Satisfação demais ajudar vocês! https://www.youtube.com/watch?v=vB43DXflzi8
0 0 0 0
Preview
Dicas de como viver e trabalhar em Portugal como dev Brasileiro Salve meus amigos! Nesse vídeo eu vou compartilhar algumas dicas de como vir a Portugal como dev Brasileiro, aqui eu abordo topicos como: documentos, sálarios, como funciona o mercado de trabalho e mais! Se esse vídeo te ajudou, curti, comenta e compartilha com os nossos amigos dev que querem vir a Europa. Satisfação demais ajudar vocês! https://www.youtube.com/watch?v=vB43DXflzi8
0 0 0 0
Preview
HTML básico para estudantes de TI Geralmente a primeira aula de HTML não condiz às expectativas dos estudantes, que empolgados com a possibilidade de enfiar mais uma página colorida cheia de animações em um endereço web, terminam o dia com uma página branca de "hello word", como uma versão mais complicada de simplesmente escrever essas palavras em um Word ou google docs. HTML é a sigla pra _Hipertext Markup Language_ , e geralmente a introdução à linguagem foca nas características de _Markup_ mais do que as de _Hipertext_. O conceito de marcação costuma ser interpretado como "demarcar áreas de conteúdo pra estilização de CSS", o que na melhor das hipóteses é meia verdade. Quando você é autor de trabalhos escritos, mesmo que não esteja seguindo ABNT, seu trabalho provavelmente tem uma capa, um título, cabeçalhos com o título do capítulo, seções com conteúdo agrupado, textos de título, subtítulo, corpo e anotações e um rodapé com as referências (links), nome dos autores e número da página. Ao pegar um trabalho impresso você provavelmente identifica o que são cada um desses elementos apenas por como eles se parecem, pela cor, tamanho da fonte, localização e contraste desses elementos, mas também porque durante a escola você foi formado pra entender essa **sintaxe visual**. Aqui fica claro que informação não são só caracteres na tela, a diferença de um título e uma citação é indicada de forma exclusivamente visual. Diferente de um trabalho de escola ou faculdade, um site pode ser veiculado pra todo canto do mundo, que talvez entenda essa sintaxe visual de forma diferente - a leitura dessas pessoas pode ser na vertical, da direita pra esquerda, o significado dos pesos, cores, pode ser diferente. Como os sites trafegam em bits que vão se complexificando até virarem informação em tela, eles não vão carregar essa sintaxe - essa **semântica** de forma visual. Logo, pra carregar essa **semântica** precisamos representar essa sintaxe visual na forma de um dado que trafegue pela internet e possa ser interpretado por diversos dispositivos e adaptado pra sintaxe visual de qualquer cultura ou nicho. Por isso HTML é um **hipertexto de marcação**. A documentação da WHATWG descreve o HTML da seguinte forma: > HTML é a linguagem de marcação central da World Wide Web. Originalmente, o HTML foi projetado principalmente como uma linguagem para **descrever semanticamente documentos científicos**. No entanto, sua concepção geral permitiu que ele fosse adaptado, ao longo dos anos subsequentes, para descrever diversos outros tipos de documentos e até mesmo aplicações. - HTML Living Stardard, Backgorund Citação original em inglês > HTML is the World Wide Web's core markup language. Originally, HTML was primarily designed as a language for semantically describing scientific documents. Its general design, however, has enabled it to be adapted, over the subsequent years, to describe a number of other types of documents and even applications. Ou seja, **o HTML é uma linguagem que descreve documentos e conteúdos de forma universal, acessivel e customizável**. ## Sites e documentos Sites e aplicações são documentos de texto como qualquer outro. A diferença de um trabalho escolar e um site é a formatação do seu conteúdo e as funcionalidades que você aplica a ele - respectivamente, papel do CSS e do Javascript. Nas primeiras aulas de front-end a distância entre as tags esquisitas que geram letras em uma página branca e um site como esse costuma parecer abismal: Imagem do site Village Montreal Mas por debaixo da formatação temos tags como qualquer outra que com sua semântica representam o conteúdo que compreendemos visualmente através do CSS. No exemplo abaixo a mesma página com as tags sobrepondo os elementos que elas representam. Por incrível que pareça é a mesma página sem formatação CSS, contendo o mesmo conteúdo e a mesma semântica. Visualmente ela é muito diferente, mas ao ser escutada através de tecnologias assistivas ou lida por um terminal, como o Browsh. Usando um script pra colorir os elementos é possível ver que mesmo a página sem CSS possui um layout, mesmo que simples. Ela tem margens, padding, tamanhos de fonte e afins. O navegador injeta esse CSS base pra que possamos visualizar os elementos mesmo sem criar estilos autorais. Sem CSS | Com CSS ---|--- | ## A sintaxe Pra entender a forma de escrever HTML, temos que entender que elementos tem pais, filhos e irmãos, tanto no layout quanto no código: Para agrupar elementos visualmente temos que colocá-los no mesmo container, no layout isso dá a sensação visual de unidade, no código faz com que as mudanças de layout sejam aplicadas apenas à componentes que se referem a aquela parcela da página ou componente. Pai (Parent) | Irmão (Sibling) ---|--- | "Pais" são elementos que possuem outros dentro, são chamados também de containers | "Irmãos" são componentes que ficam lado a lado Isso é importante pois muitos conceitos importantes do HTML e do CSS se baseiam nessa relação de _parent_ , _children_ e _sibling_. Elementos HTML são representados por tags, uma de abertura e fechamento. Elementos que não possuem filhos, como imagens e quebras de linha "fecham" já na tag de abertura: <section> <!-- <---- Abertura - elemento pai --> <p> Text </p> <!-- <---- Elemento filho --> </section> <!-- <---- Fechamento --> <img src="https://www.placehold.co/400x400" alt="Imagem de placeholder" /> <!-- Elemento img não pode receber filhos, na prática não dá pra colocar algo dentro de uma imagem dessa forma --> Tags sinalizam abertura e fechamento de um elemento, o espaço entre as tags abrigam elementos filhos. Dentro da tag é possível escrever atributos que aderem funcionalidades à tags, como classes que recebem estilos, ids que criam uma variável global com o elemento no Javascript. Existe mais de uma centena de atributos, separado em atributos específicos e globais. Os específicos são atributos que adicionam funcionalidade à elementos específicos, como o `type` que modifica o tipo de um input, que pode ser "text", "number", "tel", "date" e afins ou o atributo `form` que vincula elementos de formulário que estão fora de um elemento `<form>` a o formulário que eles se referem. > 💡 A lista completa de atributos pode ser lida no MDN, nela você pode conferir quais elementos tem determinado atributo. > > Você também pode ir na documentação da MDN do próprio elemento pra ver quais atributos são válidos nele e o que eles fazem: > ## Semântica A maioria dos sites possuem uma barra de navegação que tem o papel de disponibilizar através de links partes do site que o usuário pode acessar, tal como na imagem abaixo. Da esquerda pra direita, a barra de navegação contém um logo que ao clicar direciona pra página inicial, 5 links que direcionam pra diferentes páginas do site e um link estilizado como um botão que direciona pra página de entrar em contato. Site do estudio Analogue Simplificando (e muito) o código do site, a estrutura de HTML suficiente pra expressar essas funcionalidades seria algo como essa: <nav> <a href="./index.html"> <img src="logo.png" alt="Ir para página inicial" /> </a> <ul> <li><a href="./work">Work</a></li> <li><a href="./service">Service</a></li> <!-- etc ... --> <li><a href="./connect">Connect</a></li> </ul> </nav> * Usamos o `nav` pois ele representa estruturas que contém navegação. "Navegar" é ir de uma página a outra ou a diferentes trechos da mesma página através de links, logo toda porção do site que faz isso pode ser representada com essa tag. Ao usar o `nav` leitores de tela lêem em voz alta o trecho dentro desse container como "navegação", informando a pessoa usuária que dentro dela tem links. * Temos uma âncora (`<a>`) ao redor de uma imagem representando um logotipo clicável que direciona à pagina inicial (`index.html`). Toda âncora precisa ter um texto descrevendo onde ela vai pra que até usuários não videntes (pessoas que tem baixa ou nenhuma visão) possam ter contexto da onde o link vai. O link é lido como " link" - o texto alternativo passado no atributo `alt=` da imagem pode ser lido como texto de um link, no exemplo a cima o leitor de tela leria "Ir para página inicial, link". * Logo abaixo tem uma lista não ordenada (`<ul>`) que representa um ou mais items de lista (`<li>`) que não são representados em uma ordem específica. Dado que selecionamos o primeiro item da lista "Work", o leitor de tela leria "Work, link, lista 6 items". Usar esse formato pra todo tipo de lista, como listagem de produtos representada por cards, dá ao cliente a informação de que é uma lista e da quantidade de items que ela possuí. Usar os elementos de HTML corretos habilita: * Melhor uso de estilos, tirando a necessidade de escrever CSS adicional pra adaptar um elemento. * Habilita super poderes específicos pra elementos, como `<form>` que faz com que qualquer botão dentro dele possa submeter o formulário e possuí mecanismos de validação nativos. * Deixa o HTML legível pra pessoas não videntes, pessoas que acessam a web por outras aplicações que não navegadores e crawlers. * Robôs que lêem o conteúdo do site para indexá-lo em mecanismos de busca ou extrair conteúdo. Boa parte da acessibilidade é escrever HTML corretamente. Um site com HTML não semântico é um site "pronto" pra um publico muito menor do que você imagina. Um site só com `<div>` é impossível de navegar só com o teclado, obrigado os clientes a terem um dispositivo de ponteiro ou estarem usando as duas mãos. Imagina um site que basta seu cliente machucar a mão dominante ou ninar uma criança que ele já não consegue acessar? Esse artigo é um work in progress, na próxima edição vou falar de tipos de container e sua relação com o conteúdo.
0 0 0 0
Preview
Estrutura do projeto React para escalar: decomposition, camadas e hierarquia > _**Nota:** apenas traduzi o texto abaixo e postei aqui. As referências estão no fim deste artigo._ Como estruturar React apps "da maneira certa" parece ser o assunto do momento recentemente, desde que o React existe. A opinião oficial do React sobre isso é que ele "não tem opiniões". Isso é ótimo, nos dá liberdade total para fazer o que quisermos. E também é ruim. Isso leva a tantas opiniões fundamentalmente diferentes e muito fortes sobre a estrutura adequada do React app, que até mesmo os desenvolvedores mais experientes às vezes se sentem perdidos, sobrecarregados e precisam chorar em um canto escuro por causa disso. Eu, é claro, também tenho uma opinião forte sobre o tópico 😈. E nem vai ser "depende" dessa vez 😅 (quase). O que quero compartilhar hoje é o sistema, que vi funcionando muito bem em: * um ambiente com dezenas de equipes vagamente conectadas no mesmo repositório trabalhando no mesmo produto * em um ambiente acelerado de uma pequena startup com apenas alguns engenheiros * ou mesmo para projetos de uma pessoa (sim, eu uso isso o tempo todo para minhas coisas pessoais) Só lembre-se, assim como o Código do Pirata, tudo isso é mais o que você chamaria de "diretrizes" do que regras reais. ## O que precisamos da convenção de estrutura de projeto Não quero entrar em detalhes sobre por que precisamos de convenções como essa em primeiro lugar: se você chegou a este artigo, provavelmente já decidiu que precisa dela. O que eu quero falar um pouco, antes de pular para soluções, é o que torna uma convenção de estrutura de projeto ótima. ### Replicabilidade A convenção de código deve ser compreensível e fácil o suficiente para ser reproduzida por qualquer membro da equipe, incluindo um estagiário recém-contratado com experiência mínima em React. Se a maneira de trabalhar em seu repositório exige um PhD, alguns meses de treinamento e debates profundamente filosóficos sobre cada segundo PR... Bem, provavelmente será um sistema realmente bonito, mas não existirá em nenhum outro lugar além do papel. ### Inferrabilidade Você pode escrever um livro e gravar alguns filmes sobre "A maneira de trabalhar em nosso repositório". Você provavelmente pode até convencer todos na equipe a ler e assistir (embora você provavelmente não vá). O fato é: a maioria das pessoas não vai memorizar cada palavra, se é que vai. Para que a convenção realmente funcione, ela deve ser tão óbvia e intuitiva, de modo que as pessoas na equipe idealmente sejam capazes de fazer engenharia reversa apenas lendo o código. No mundo perfeito, assim como com comentários de código, você nem precisaria escrevê-lo em lugar nenhum - o código e a estrutura em si seriam sua documentação. ### Independência Um dos requisitos mais importantes das diretrizes de estrutura de codificação para várias pessoas, e especialmente várias equipes, é solidificar uma maneira para os desenvolvedores operarem independentemente. A última coisa que você quer é vários desenvolvedores trabalhando no mesmo arquivo, ou equipes constantemente invadindo as áreas de responsabilidade umas das outras. Portanto, nossas diretrizes de estrutura de codificação devem fornecer tal estrutura, onde as equipes sejam capazes de coexistir pacificamente dentro do mesmo repositório. ### Otimizado para refatoração Último, mas no mundo moderno do frontend, é o mais importante. O frontend hoje em dia é incrivelmente fluido. Patterns, frameworks e melhores práticas estão mudando constantemente. Além disso, espera-se que entreguemos features rapidamente hoje em dia. Não, RÁPIDO. E então reescrevê-lo completamente depois de um mês. E então talvez reescrevê-lo novamente. Então se torna muito importante para nossa convenção de codificação não nos forçar a "colar" o código em algum lugar permanente sem nenhuma maneira de movê-lo. Ela deve organizar as coisas de tal forma que a refatoração seja algo que seja realizado casualmente em uma base diária. A pior coisa que uma convenção pode fazer é tornar a refatoração tão difícil e demorada que todos fiquem apavorados com ela. Em vez disso, deve ser tão simples quanto respirar. ... Agora que temos nossos requisitos gerais para a convenção de estrutura do projeto, é hora de entrar em detalhes. Vamos começar com o panorama geral e, então, detalhar. ## Organizando o projeto em si: decomposition A primeira e mais importante parte da organização de um grande projeto que esteja alinhado com os princípios que definimos acima é a "decomposition": em vez de pensar nisso como um projeto monolítico, pode ser pensado como uma composição de recursos mais ou menos independentes. A boa e velha discussão "monólito" vs "microsserviços", apenas dentro de um React app. Com essa abordagem, cada feature é essencialmente um "nanoserviço" de certa forma, que é isolado do restante das features e se comunica com elas por meio de uma "API" externa (geralmente apenas React props). Mesmo apenas seguindo essa mentalidade, comparado à abordagem mais tradicional do "projeto React", você terá praticamente tudo da nossa lista acima: equipes/pessoas poderão trabalhar independentemente em features em paralelo se as implementarem como um monte de "black boxes" conectadas umas às outras. Se a configuração estiver correta, deve ser bem óbvio para qualquer um também, só exigiria um pouco de prática para se ajustar à mudança de mentalidade. Se você precisar remover uma feature, você pode simplesmente "desconectá-la" ou substituí-la por outra feature. Ou se você precisar refatorar os detalhes internos de uma feature, você pode fazer isso. E enquanto a "API" pública dele permanecer funcional, ninguém de fora vai nem perceber. Estou descrevendo um React component, não é? 😅 Bem, o conceito é o mesmo, e isso torna o React perfeito para essa mentalidade. Eu definiria uma "feature", para distingui-lo de um "component", como "um monte de components e outros elements unidos em uma funcionalidade completa da perspectiva do usuário final". Agora, como organizar isso para um único projeto? Especialmente considerando que, comparado a microsserviços, ele deve vir com muito menos encanamento: em um projeto com centenas de features, extraí-los todos para microsserviços reais será quase impossível. O que podemos fazer em vez disso é usar a arquitetura multi-package monorepo: é perfeita para organizar e isolar features independentes como packages. Um package é um conceito que já deve ser familiar para qualquer um que instalou algo do npm. E um monorepo - é apenas um repo, onde você tem o código-fonte de vários packages vivendo juntos em harmonia, compartilhando ferramentas, scripts, dependências e, às vezes, uns aos outros. Então o conceito é simples: projeto React → dividi-lo em features independentes → colocar essas features em packages. Se você nunca trabalhou com monorepo configurado localmente e agora, depois que mencionei "package" e "npm", se sente desconfortável com a ideia de publicar seu projeto privado: não fique. Nem publicação nem código aberto são requisitos para que um monorepo exista e para que os desenvolvedores obtenham os benefícios dele. Da perspectiva do código, um package é apenas uma pasta, que tem o arquivo `package.json` com algumas propriedades. Essa pasta é então linked por meio dos links simbólicos do Node à pasta `node_modules`, onde os packages "tradicionais" são instalados. Esse linking é realizado por ferramentas como Yarn ou Npm: é chamada de "workspaces", e ambos oferecem suporte a isso. E eles tornam os packages acessíveis em seu código local como qualquer outro package baixado do npm. Ficaria assim: /packages /my-feature /some-folders-in-feature index.ts package.json // isto é o que define o my-feature package /another-feature /some-folders-in-feature index.ts package.json // isto é o que define o another-feature package e no package.json eu teria esses dois campos importantes: { "name": "@project/my-feature", "main": "index.ts" } Onde o campo "name" é, obviamente, o nome do package - basicamente o alias para esta pasta, através do qual ele será acessível ao código no repositório. E "main" é o entry point principal para o package, ou seja, qual arquivo será importado quando eu escrever algo como import { Something } from '@project/my-feature'; Existem alguns repositórios públicos de projetos conhecidos que usam a abordagem monorepo de vários packages: Babel, React, Jest, para citar alguns. ### Por que packages em vez de apenas folders À primeira vista, a abordagem dos packages parece "apenas dividir seus recursos em folders, qual é o problema" e não parece tão inovadora. No entanto, há algumas coisas interessantes que os packages podem nos dar, que folders simples não podem. Alias. Com packages, você pode se referir à sua feature pelo nome, não pela localização. Compare isto: import { Button } from '@project/button'; com esta abordagem mais "tradicional": import { Button } from '../../components/button'; No primeiro import, é óbvio - estou usando um generic component de "button" do meu projeto, minha versão de design system. Na segunda, não está tão claro - o que é esse button? É o generic button de "design system"? Ou talvez parte dessa feature? Ou uma feature "acima"? Posso usá-la aqui, talvez tenha sido escrito para algum caso de uso muito específico que não vai funcionar na minha nova feature? Fica ainda pior se você tiver vários folders "utils" ou "common" no seu repositório. Meu pior pesadelo de código se parece com isso: import { bla } from '../../../common'; import { blabla } from '../../common'; import { blablabla } from '../common'; Com packages, poderia ser algo como isto: import { bla } from '@project/button/common'; import { blabla } from '@project/something/common'; import { blablabla } from '@project/my-feature/common'; Instantaneamente óbvio o que vem de onde e o que pertence a onde. E as chances são de que o código "my-feature" "common" foi escrito apenas para uso interno da feature, nunca foi feito para ser usado fora da feature, e reutilizá-lo em outro lugar é uma má ideia. Com pacotes, você verá isso imediatamente. _**Separation of concerns**_. Considerando que todos nós estamos acostumados com os npm packages e o que eles representam, fica muito mais fácil pensar sobre sua feature como um module isolado com sua própria API pública quando ele é escrito como um "package" imediatamente. Dê uma olhada nisso: import { dateTimeConverter } from '../../../../button/something/common/date-time-converter'; vs isso: import { dateTimeConverter } from '@project/button'; O primeiro provavelmente se perderá em todos os imports ao redor e passará despercebido, transformando seu código em The Big Ball of Mud. O segundo levantará algumas sobrancelhas instantâneamente e naturalmente: um date time converter? De um button? Sério? O que naturalmente forçará limites mais claros entre diferentes features/packages. **Suporte integrado**. Você não precisa inventar nada, a maioria das ferramentas modernas, como IDE, typescript, linting ou bundlers oferecem suporte a packages prontos para uso. **A refatoração é fácil**. Com as features separadas em packages, a refatoração se torna agradável. Quer refatorar o conteúdo do seu package? Vá em frente, você pode reescrevê-lo completamente, desde que mantenha a entry da API a mesma, o resto do repositório nem notará. Quer mover seu package para outro local? É só arrastar e soltar de um folder, se você não renomeá-la, o resto do repositório não será afetado. Quer renomear o package? Basta pesquisar e substituir uma string no projeto, nada mais. Entry points explícitos. Você pode ser muito específico sobre o que exatamente de um package está disponível para os consumidores externos se quiser realmente adotar a mentalidade de "somente API pública para os consumidores". Por exemplo, você pode restringir todos os imports "profundos", tornar coisas como `@project/button/some/deep/path` impossíveis e forçar todos a usar somente API pública explicitamente definida no arquivo `index.ts`. Dê uma olhada em Package entry points e a documentação de Package exports para exemplos de como isso funciona. ## Como split o código dentro de packages A maior dificuldade das pessoas na arquitetura multi-package é qual é o momento certo para extrair código em um package? Cada pequena feature deve ser um? Ou talvez os packages sejam apenas para coisas grandes, como uma página inteira ou até mesmo um app? Na minha experiência, há um equilíbrio aqui. Você não quer extrair cada pequena coisa em um package: você acabará com apenas uma lista plana de centenas de packages minúsculos de um único arquivo sem estrutura, o que meio que anula o propósito de introduzi-los em primeiro lugar. Ao mesmo tempo, você não gostaria que seu package se tornasse muito grande: você atingirá todos os problemas que estamos tentando resolver aqui, apenas dentro desse package. Aqui estão alguns limites que eu costumo usar: * "design system" type das coisas como buttons, modal dialogs, layouts, tooltips, etc., todos devem ser packages * features em alguns limites de IU "naturais" são bons candidatos para um package - ou seja, algo que vive em um modal dialog, em uma drawer, em um slide-in panel, etc. * features "compartilháveis" - aquelas que podem ser usadas ​​em vários lugares * algo que você pode descrever como uma "feature" isolada com limites claros, lógicos e idealmente visíveis na IU Além disso, assim como no artigo anterior sobre como dividir o código em components, é muito importante que um package seja responsável apenas por uma coisa conceitual. Um package que exporta um Button, CreateIssueDialog e DateTimeConverter faz muitas coisas ao mesmo tempo e precisa ser "split up". ### Como organizar packages Embora seja possível criar apenas uma lista simples de todos os packages, e para certos tipos de projetos isso funcionaria, para produtos grandes e pesados ​​de UI provavelmente não será o suficiente. Ver algo como packages "tooltip" e "settings page" juntos me faz estremecer 😖. Ou pior - se você tiver packages "backend" e "frontend" juntos. Isso não é apenas confuso, mas também perigoso: a última coisa que você quer é acidentalmente pull algum código "backend" para seu frontend bundle. A estrutura real do repositório dependeria muito do que exatamente é o produto que você está implementando (ou mesmo quantos produtos existem), se você tem backend ou apenas frontend, e provavelmente mudará e evoluirá significativamente ao longo do tempo. Felizmente, esta é a grande vantagem dos packages: a estrutura real é completamente independente do código, você pode drag-and-drop eles e reestruturá-los uma vez por semana sem quaisquer consequências se houver necessidade. Considerando que o custo do "erro" na estrutura é bastante baixo, não há necessidade de pensar demais, pelo menos no início. Se o seu projeto for somente frontend, você pode até começar com uma lista simples: /packages /button ... /footer /settings ... e evoluir ao longo do tempo para algo como isto: /packages /core /button /modal /tooltip ... /product-one /footer /settings ... /product-two ... Ou, se você tiver um backend, poderia ser algo assim: /packages /frontend ... // o mesmo que acima /backend ... // alguns backend packages específicos /common ... // alguns packages que são que são conpartilhados entre frontend e backend Onde em "common" você colocaria algum código que fosse compartilhado entre frontend e backend. Normalmente serão algumas configs, constants, utils do tipo lodash, types compartilhados. ## Como estruturar um package em si Para resumir a grande seção acima: "use monorepo, extraia features em packages". 🙂 Agora para a próxima parte - como organizar o package em si. Três coisas são importantes aqui para mim: naming convention, separar o package em layers distintas e hierarquia estrita. ### Naming convention Todo mundo adora nomear coisas e debater sobre o quão ruins são as outras coisas, não é? Para reduzir o tempo gasto em intermináveis ​​​​tópicos de comentários do GitHub e "calm down poor geeks" com TOC relacionado ao código como eu, é melhor concordar com uma "convenção de nomenclatura" de uma vez para todos. Qual usar não importa muito na minha opinião, desde que seja seguido consistentemente ao longo do projeto. Se você tem `ReactFeatureHere.ts` e `react-feature-here.ts` no mesmo repositório, um gatinho chora em algum lugar 😿. Eu costumo usar este: /my-feature-name /assets // se eu tiver algumas imagens, elas vão para um folder próprio logo.svg index.tsx // código principal da feature test.tsx // tests para a feature se necessário stories.tsx // stories para storybooks se eu usar elas styles.(tsx|scss) // Gosto de separar os styles da lógica dos components types.ts // se os types forem compartilhados entre diferentes arquivos dentro da feature utils.ts // utils muito simples que são usados ​​*apenas* nesta feature hooks.tsx // pequenas hooks que eu uso *somente* nesta feature Se uma feature tiver alguns components menores imported diretamente para `index.tsx`, eles ficarão assim: /my-feature-name ... // o mesmo que antes header.tsx header.test.tsx header.styles.tsx ... // etc ou, mais provavelmente, eu extrairia eles imediatamente para folders e eles ficariam assim: /my-feature-name ... // index o mesmo de antes /header index.tsx ... // etc, exatamente o mesmo naming aqui /footer index.tsx ... // etc, exatamente o mesmo naming aqui A abordagem de folders é muito mais otimizada para desenvolvimento orientado a copiar e colar 😊: ao criar uma nova feature copiando e colando a estrutura da feature próxima, tudo o que você precisa fazer é renomear apenas um folder. Todos os arquivos serão nomeados exatamente da mesma forma. Além disso, é mais fácil criar um modelo mental do package, refatorar e mover o código (sobre isso na próxima seção). ### Layers dentro de um package Um package típico com uma feature complicada teria algumas "layers" distintas: pelo menos a layer "UI" e a layer "Data". Embora seja provavelmente possível misturar tudo, eu ainda recomendaria não fazer isso: renderizar buttons e buscar dados do backend são preocupações muito diferentes. Separá-los dará ao package mais estrutura e previsibilidade. E para que o projeto permaneça relativamente saudável em termos de arquitetura e código, o crucial é ser capaz de identificar claramente as layers que são importantes para seu app, mapear o relacionamento entre elas e organizar tudo isso de uma forma que esteja alinhada com quaisquer ferramentas e frameworks usados. Se eu estivesse implementando um projeto React do zero hoje, com Graphql para manipulações de dados e React state puro para gerenciamento de state (ou seja, sem Redux ou qualquer outra biblioteca), eu teria as seguintes layers: * "Data" layer - queries, mutations e outras coisas que são responsáveis ​​por conectar-se às fontes de dados externas e transformá-las. Usado apenas pela UI layer, não depende de nenhuma outra layer. * "Shared" layer - vários utils, functions, hooks, mini-components, types e constants que são usados ​​em todo o package por todas as outras layers. Não depende de nenhuma outra layer. * "ui" layer - a implementação real da feature. Depende das layers "data" e "shared", ninguém depende dela. É isso! Se eu estivesse usando alguma biblioteca externa de gerenciamento de state, provavelmente adicionaria a layer "state" também. Essa provavelmente seria uma ponte entre "data" e "ui", e portanto usaria as layers "shared" e "data" e "UI" usaria "state" em vez de "data". E do ponto de vista dos detalhes da implementação, todas as layers são top-level folders em um package: /my-feature-package /shared /ui /data index.ts package.json Com cada "layer" usando a mesma naming convention descrita acima. Então sua "data" layer ficaria mais ou menos assim: /data index.ts get-some-data.ts get-some-data.test.ts update-some-data.ts update-some-data.test.ts Para packages mais complicados, eu poderia split essas layers, preservando seu propósito e suas características. A layer "Data" poderia ser split em "queries" ("getters") e "mutations" ("setters"), por exemplo, e essas podem permanecer no folder "data" ou subir: /my-feature-package /shared /ui /queries /mutations index.ts package.json Ou você pode extrair algumas sub-layers da "shared" layer, como "types" e "UI components compartilhados" (o que transformaria instantaneamente essa sub-layer no tipo "IU" a propósito, já que ninguém além de "IU" pode usar UI components). /my-feature-package /shared-ui /ui /queries /mutations /types index.ts package.json Contanto que você consiga definir claramente qual é o propósito de cada "sublayer", saber qual "sublayer" pertence a qual "layer" e conseguir visualizar e explicar isso para todos na equipe - tudo funciona! ### Hierarquia rigorosa dentro das layers A peça final do quebra-cabeça, que torna essa arquitetura previsível e sustentável, é uma hierarquia rigorosa dentro das layers. Isso será especialmente visível na IU layer, já que em React apps ela geralmente é a mais complicada. Vamos começar, por exemplo, a estruturar uma página simples, com um header e um footer. Teríamos o arquivo "index.ts" - o arquivo principal, onde a página se reúne, e os components "header.ts" e "footer.ts". /my-page index.ts header.ts footer.ts Agora, todos eles terão seus próprios components que eu gostaria de colocar em seus próprios arquivos. "Header", por exemplo, terá os components "Search bar" e "Send feedback". Na maneira flat "tradicional" de organizar apps, nós colocaríamos eles um ao lado do outro, não é? Seria algo assim: /my-page index.ts header.ts footer.ts search-bar.ts send-feedback.ts E então, se eu quiser adicionar o mesmo button "send-feedback" ao footer component, eu novamente faria import dele para "footer.ts" de "send-feedback.ts", certo? Afinal, ele está próximo e parece natural. Infelizmente, o que aconteceu foi que violamos os limites entre nossas layers ("UI" e "shared") sem nem perceber. Se eu continuasse adicionando mais e mais components a essa estrutura flat, e provavelmente continuarei, applications reais tendem a ser bem complicadas, provavelmente violarei elas mais algumas vezes. Isso transformará esse folder em sua própria "bola de lama", onde é completamente imprevisível qual component depende de qual. E, como resultado, desembaraçar tudo isso e extrair algo desse folder, quando chegar a hora da refatoração, pode se tornar um exercício muito complicado. Em vez disso, podemos estruturar essa layer de forma hierárquica. As regras são: * apenas os arquivos principais (por exemplo, "index.ts") em uma pasta podem ter subcomponents (sub-modules) e podem import eles * você pode import apenas dos "children", não dos "vizinhos" * você não pode pular um level e pode import apenas dos children diretos Ou, se preferir visual, é apenas uma árvore: E se você precisar compartilhar algum código entre diferentes levels dessa hierarquia (como nosso send-feedback component), você verá instantaneamente que está violando as regras da hierarquia, já que onde quer que você coloque ele, você terá que import ele dos parents ou dos vizinhos. Então, em vez disso, ele seria extraído para a layer "shared" e imported de lá. Ficaria assim: /my-page /shared send-feedback.ts /ui index.ts /header index.ts search-bar.ts /footer index.ts Dessa forma, a IU layer (ou qualquer layer onde essa regra se aplica) se transforma em uma estrutura de árvore, onde cada branch é independente de qualquer outro branch. Extrair qualquer coisa desse package agora é moleza: tudo o que você precisa fazer é drag e drop um folder em um novo lugar. E você sabe com certeza que nenhum component na árvore da IU será afetado por ela, exceto aquele que realmente usa ele. A única coisa com a qual você pode precisar lidar adicionalmente é a "shared" layer. O app completo com a data layer ficaria assim: Algumas layers claramente definidas, completamente encapsuladas e previsíveis. /my-page /shared send-feedback.ts /data get-something.ts send-something.ts /ui index.ts /header index.ts search-bar.ts /footer index.ts ### React recomenda não aninhar Se você ler a documentação do React sobre a estrutura de projeto recomendada, verá que React realmente recomenda não aninhar muito. A recomendação oficial é "considere limitar-se a um máximo de três ou quatro pastas aninhadas dentro de um único projeto". E essa recomendação é muito relevante para essa abordagem também: se seu package ficar muito aninhado, é um sinal claro de que você pode precisar pensar em splitting ele em packages menores. 3-4 níveis de aninhamento, na minha experiência, são suficientes até mesmo para features muito complicadas. A beleza da arquitetura de packages, porém, é que você pode organizar seus packages com tanto aninhamento quanto precisar sem ficar preso a essa restrição - você nunca se refere a outro package por seu relative path, apenas por seu nome. Um package com o nome `@project/change-setting-dialog` que reside no path `packages/change-settings-dialog` ou está oculto dentro de `/packages/product/features/settings-page/change-setting-dialog`, será chamado de `@project/change-setting-dialog` independentemente de sua localização física. ### Ferramenta de gerenciamento Monorepo É impossível falar sobre multi-packages monorepo para sua arquitetura sem tocar pelo menos um pouco nas ferramentas de gerenciamento monorepo. O maior problema geralmente é o gerenciamento de dependências dentro dele. Imagine se alguns dos seus monorepo packages usam uma dependência externa, `lodash` por exemplo. /my-feature-one package.json // este usa lodash@3.4.5 /my-other-feature package.json // este usa lodash@3.4.5 Agora o lodash lança uma nova versão, `lodash@4.0.0`, e você quer mover seu projeto para ele. Você precisaria atualizá-lo em todos os lugares ao mesmo tempo: a última coisa que você quer é que alguns dos packages permaneçam na versão antiga, enquanto outros usem a nova. Se você estiver no `npm` ou no antigo `yarn`, isso seria um desastre: eles instalariam várias cópias (não duas, várias) do `lodash` em seu sistema, o que resultaria em tempos maiores de instalação e build, e seus tamanhos de bundles disparando. Sem mencionar a diversão de desenvolver uma nova feature quando você está usando duas versões diferentes da mesma biblioteca em todo o projeto. Não vou tocar no que usar se seu projeto for publicado no npm e de código aberto: provavelmente algo como Lerna seria o suficiente, mas esse é um tópico completamente diferente. Se, no entanto, seu repositório for **private** , as coisas estão ficando mais interessantes. Porque tudo o que você realmente precisa para que essa arquitetura funcione é packages "aliasing", nada mais. Ou seja, apenas links simbólicos básicos que tanto o Yarn quanto o Npm fornecem por meio da ideia de workspaces. Parece com isso. Você tem o arquivo "root" `package.json`, onde você declara onde estão os workspaces (ou seja, seus packages locais): { "private": true, "workspaces": ["packages/**"] } E então, da próxima vez que você executar `yarn install`, todos os packages do folder packages se tornarão packages "adequados" e estarão disponíveis no seu projeto por meio dos name deles. Esse é todo o monorepo setup! Quanto às dependências. O que acontecerá se você tiver a mesma dependência em alguns packages? /packages /my-feature-one package.json // este usa lodash@3.4.5 /my-other-feature package.json // este usa lodash@3.4.5 Quando você executa `yarn install`, ele irá "hoist" (içar) esse package para o root `node_modules`: /node_modules lodash@3.4.5 /packages /my-feature-one package.json // este usa lodash@3.4.5 /my-other-feature package.json // este usa lodash@3.4.5 Esta é exatamente a mesma situação que se você declarasse `lodash@3.4.5` apenas no root `package.json`. O que estou dizendo é, e provavelmente serei enterrado vivo pelos puristas da internet por isso, incluindo eu mesmo há dois anos: você não precisa declarar nenhuma das dependências em seus packages locais. Tudo pode ir para o root `package.json`. E seus arquivos `package.json` em packages locais serão apenas arquivos `json` muito leves, que especificam apenas os fields "name" e "main". Configuração muito mais fácil de gerenciar, especialmente se você estiver apenas começando. ## Estrutura do projeto React para scale: visão geral final Huh, isso foi muito texto. E mesmo isso é apenas uma breve visão geral: muitas outras coisas podem ser ditas sobre o tópico! Vamos recapitular o que já foi dito, pelo menos: **Decomposition** é a chave para scale com sucesso seu React app. Pense no seu projeto não como um "projeto" monolítico, mas como uma combinação de "features" independentes do tipo caixa-preta com sua própria API pública para os consumidores usarem. A mesma discussão de "monólito" vs "microservices" realmente. **Arquitetura Monorepo** é perfeita para isso. Extraia suas features em packages; organize seus packages da maneira que melhor funciona para seu projeto. **Layers** dentro de um package são importantes para dar a ele alguma estrutura. Você provavelmente terá pelo menos uma "data" layer, uma "UI" layer e uma "shared" layer. Pode introduzir mais, dependendo de suas necessidades, só precisa ter limites claros entre elas. **Estrutura hierárquica** de um package é legal. Ela facilita a refatoração, força você a ter limites mais claros entre as layers e força você a dividir seu package em outros menores quando ele se torna muito grande. **Gerenciamento de dependências** em um monorepo é um tópico complicado, mas se seu projeto for private, você não precisa se preocupar com isso. Apenas declare todas as suas dependências no root package.json e mantenha todos os packages locais livres delas. Você pode dar uma olhada na implementação dessa arquitetura neste repositório de exemplo: https://github.com/developerway/example-react-project. Este é apenas um exemplo básico para demonstrar os princípios descritos no artigo, então não se assuste com packages pequenos com apenas um `index.ts`: em um app real, eles serão muito maiores. Isso é tudo por hoje. Espero que você consiga aplicar alguns desses princípios (ou até mesmo todos eles!) aos seus apps e veja melhorias no seu desenvolvimento diário imediatamente! ✌🏼 ## Fonte Artigo escrito por **Nadia Makarevich**.
0 0 0 0
Preview
Estrutura do projeto React para escalar: decomposition, camadas e hierarquia > _**Nota:** apenas traduzi o texto abaixo e postei aqui. As referências estão no fim deste artigo._ Como estruturar React apps "da maneira certa" parece ser o assunto do momento recentemente, desde que o React existe. A opinião oficial do React sobre isso é que ele "não tem opiniões". Isso é ótimo, nos dá liberdade total para fazer o que quisermos. E também é ruim. Isso leva a tantas opiniões fundamentalmente diferentes e muito fortes sobre a estrutura adequada do React app, que até mesmo os desenvolvedores mais experientes às vezes se sentem perdidos, sobrecarregados e precisam chorar em um canto escuro por causa disso. Eu, é claro, também tenho uma opinião forte sobre o tópico 😈. E nem vai ser "depende" dessa vez 😅 (quase). O que quero compartilhar hoje é o sistema, que vi funcionando muito bem em: * um ambiente com dezenas de equipes vagamente conectadas no mesmo repositório trabalhando no mesmo produto * em um ambiente acelerado de uma pequena startup com apenas alguns engenheiros * ou mesmo para projetos de uma pessoa (sim, eu uso isso o tempo todo para minhas coisas pessoais) Só lembre-se, assim como o Código do Pirata, tudo isso é mais o que você chamaria de "diretrizes" do que regras reais. ## O que precisamos da convenção de estrutura de projeto Não quero entrar em detalhes sobre por que precisamos de convenções como essa em primeiro lugar: se você chegou a este artigo, provavelmente já decidiu que precisa dela. O que eu quero falar um pouco, antes de pular para soluções, é o que torna uma convenção de estrutura de projeto ótima. ### Replicabilidade A convenção de código deve ser compreensível e fácil o suficiente para ser reproduzida por qualquer membro da equipe, incluindo um estagiário recém-contratado com experiência mínima em React. Se a maneira de trabalhar em seu repositório exige um PhD, alguns meses de treinamento e debates profundamente filosóficos sobre cada segundo PR... Bem, provavelmente será um sistema realmente bonito, mas não existirá em nenhum outro lugar além do papel. ### Inferrabilidade Você pode escrever um livro e gravar alguns filmes sobre "A maneira de trabalhar em nosso repositório". Você provavelmente pode até convencer todos na equipe a ler e assistir (embora você provavelmente não vá). O fato é: a maioria das pessoas não vai memorizar cada palavra, se é que vai. Para que a convenção realmente funcione, ela deve ser tão óbvia e intuitiva, de modo que as pessoas na equipe idealmente sejam capazes de fazer engenharia reversa apenas lendo o código. No mundo perfeito, assim como com comentários de código, você nem precisaria escrevê-lo em lugar nenhum - o código e a estrutura em si seriam sua documentação. ### Independência Um dos requisitos mais importantes das diretrizes de estrutura de codificação para várias pessoas, e especialmente várias equipes, é solidificar uma maneira para os desenvolvedores operarem independentemente. A última coisa que você quer é vários desenvolvedores trabalhando no mesmo arquivo, ou equipes constantemente invadindo as áreas de responsabilidade umas das outras. Portanto, nossas diretrizes de estrutura de codificação devem fornecer tal estrutura, onde as equipes sejam capazes de coexistir pacificamente dentro do mesmo repositório. ### Otimizado para refatoração Último, mas no mundo moderno do frontend, é o mais importante. O frontend hoje em dia é incrivelmente fluido. Patterns, frameworks e melhores práticas estão mudando constantemente. Além disso, espera-se que entreguemos features rapidamente hoje em dia. Não, RÁPIDO. E então reescrevê-lo completamente depois de um mês. E então talvez reescrevê-lo novamente. Então se torna muito importante para nossa convenção de codificação não nos forçar a "colar" o código em algum lugar permanente sem nenhuma maneira de movê-lo. Ela deve organizar as coisas de tal forma que a refatoração seja algo que seja realizado casualmente em uma base diária. A pior coisa que uma convenção pode fazer é tornar a refatoração tão difícil e demorada que todos fiquem apavorados com ela. Em vez disso, deve ser tão simples quanto respirar. ... Agora que temos nossos requisitos gerais para a convenção de estrutura do projeto, é hora de entrar em detalhes. Vamos começar com o panorama geral e, então, detalhar. ## Organizando o projeto em si: decomposition A primeira e mais importante parte da organização de um grande projeto que esteja alinhado com os princípios que definimos acima é a "decomposition": em vez de pensar nisso como um projeto monolítico, pode ser pensado como uma composição de recursos mais ou menos independentes. A boa e velha discussão "monólito" vs "microsserviços", apenas dentro de um React app. Com essa abordagem, cada feature é essencialmente um "nanoserviço" de certa forma, que é isolado do restante das features e se comunica com elas por meio de uma "API" externa (geralmente apenas React props). Mesmo apenas seguindo essa mentalidade, comparado à abordagem mais tradicional do "projeto React", você terá praticamente tudo da nossa lista acima: equipes/pessoas poderão trabalhar independentemente em features em paralelo se as implementarem como um monte de "black boxes" conectadas umas às outras. Se a configuração estiver correta, deve ser bem óbvio para qualquer um também, só exigiria um pouco de prática para se ajustar à mudança de mentalidade. Se você precisar remover uma feature, você pode simplesmente "desconectá-la" ou substituí-la por outra feature. Ou se você precisar refatorar os detalhes internos de uma feature, você pode fazer isso. E enquanto a "API" pública dele permanecer funcional, ninguém de fora vai nem perceber. Estou descrevendo um React component, não é? 😅 Bem, o conceito é o mesmo, e isso torna o React perfeito para essa mentalidade. Eu definiria uma "feature", para distingui-lo de um "component", como "um monte de components e outros elements unidos em uma funcionalidade completa da perspectiva do usuário final". Agora, como organizar isso para um único projeto? Especialmente considerando que, comparado a microsserviços, ele deve vir com muito menos encanamento: em um projeto com centenas de features, extraí-los todos para microsserviços reais será quase impossível. O que podemos fazer em vez disso é usar a arquitetura multi-package monorepo: é perfeita para organizar e isolar features independentes como packages. Um package é um conceito que já deve ser familiar para qualquer um que instalou algo do npm. E um monorepo - é apenas um repo, onde você tem o código-fonte de vários packages vivendo juntos em harmonia, compartilhando ferramentas, scripts, dependências e, às vezes, uns aos outros. Então o conceito é simples: projeto React → dividi-lo em features independentes → colocar essas features em packages. Se você nunca trabalhou com monorepo configurado localmente e agora, depois que mencionei "package" e "npm", se sente desconfortável com a ideia de publicar seu projeto privado: não fique. Nem publicação nem código aberto são requisitos para que um monorepo exista e para que os desenvolvedores obtenham os benefícios dele. Da perspectiva do código, um package é apenas uma pasta, que tem o arquivo `package.json` com algumas propriedades. Essa pasta é então linked por meio dos links simbólicos do Node à pasta `node_modules`, onde os packages "tradicionais" são instalados. Esse linking é realizado por ferramentas como Yarn ou Npm: é chamada de "workspaces", e ambos oferecem suporte a isso. E eles tornam os packages acessíveis em seu código local como qualquer outro package baixado do npm. Ficaria assim: /packages /my-feature /some-folders-in-feature index.ts package.json // isto é o que define o my-feature package /another-feature /some-folders-in-feature index.ts package.json // isto é o que define o another-feature package e no package.json eu teria esses dois campos importantes: { "name": "@project/my-feature", "main": "index.ts" } Onde o campo "name" é, obviamente, o nome do package - basicamente o alias para esta pasta, através do qual ele será acessível ao código no repositório. E "main" é o entry point principal para o package, ou seja, qual arquivo será importado quando eu escrever algo como import { Something } from '@project/my-feature'; Existem alguns repositórios públicos de projetos conhecidos que usam a abordagem monorepo de vários packages: Babel, React, Jest, para citar alguns. ### Por que packages em vez de apenas folders À primeira vista, a abordagem dos packages parece "apenas dividir seus recursos em folders, qual é o problema" e não parece tão inovadora. No entanto, há algumas coisas interessantes que os packages podem nos dar, que folders simples não podem. Alias. Com packages, você pode se referir à sua feature pelo nome, não pela localização. Compare isto: import { Button } from '@project/button'; com esta abordagem mais "tradicional": import { Button } from '../../components/button'; No primeiro import, é óbvio - estou usando um generic component de "button" do meu projeto, minha versão de design system. Na segunda, não está tão claro - o que é esse button? É o generic button de "design system"? Ou talvez parte dessa feature? Ou uma feature "acima"? Posso usá-la aqui, talvez tenha sido escrito para algum caso de uso muito específico que não vai funcionar na minha nova feature? Fica ainda pior se você tiver vários folders "utils" ou "common" no seu repositório. Meu pior pesadelo de código se parece com isso: import { bla } from '../../../common'; import { blabla } from '../../common'; import { blablabla } from '../common'; Com packages, poderia ser algo como isto: import { bla } from '@project/button/common'; import { blabla } from '@project/something/common'; import { blablabla } from '@project/my-feature/common'; Instantaneamente óbvio o que vem de onde e o que pertence a onde. E as chances são de que o código "my-feature" "common" foi escrito apenas para uso interno da feature, nunca foi feito para ser usado fora da feature, e reutilizá-lo em outro lugar é uma má ideia. Com pacotes, você verá isso imediatamente. _**Separation of concerns**_. Considerando que todos nós estamos acostumados com os npm packages e o que eles representam, fica muito mais fácil pensar sobre sua feature como um module isolado com sua própria API pública quando ele é escrito como um "package" imediatamente. Dê uma olhada nisso: import { dateTimeConverter } from '../../../../button/something/common/date-time-converter'; vs isso: import { dateTimeConverter } from '@project/button'; O primeiro provavelmente se perderá em todos os imports ao redor e passará despercebido, transformando seu código em The Big Ball of Mud. O segundo levantará algumas sobrancelhas instantâneamente e naturalmente: um date time converter? De um button? Sério? O que naturalmente forçará limites mais claros entre diferentes features/packages. **Suporte integrado**. Você não precisa inventar nada, a maioria das ferramentas modernas, como IDE, typescript, linting ou bundlers oferecem suporte a packages prontos para uso. **A refatoração é fácil**. Com as features separadas em packages, a refatoração se torna agradável. Quer refatorar o conteúdo do seu package? Vá em frente, você pode reescrevê-lo completamente, desde que mantenha a entry da API a mesma, o resto do repositório nem notará. Quer mover seu package para outro local? É só arrastar e soltar de um folder, se você não renomeá-la, o resto do repositório não será afetado. Quer renomear o package? Basta pesquisar e substituir uma string no projeto, nada mais. Entry points explícitos. Você pode ser muito específico sobre o que exatamente de um package está disponível para os consumidores externos se quiser realmente adotar a mentalidade de "somente API pública para os consumidores". Por exemplo, você pode restringir todos os imports "profundos", tornar coisas como `@project/button/some/deep/path` impossíveis e forçar todos a usar somente API pública explicitamente definida no arquivo `index.ts`. Dê uma olhada em Package entry points e a documentação de Package exports para exemplos de como isso funciona. ## Como split o código dentro de packages A maior dificuldade das pessoas na arquitetura multi-package é qual é o momento certo para extrair código em um package? Cada pequena feature deve ser um? Ou talvez os packages sejam apenas para coisas grandes, como uma página inteira ou até mesmo um app? Na minha experiência, há um equilíbrio aqui. Você não quer extrair cada pequena coisa em um package: você acabará com apenas uma lista plana de centenas de packages minúsculos de um único arquivo sem estrutura, o que meio que anula o propósito de introduzi-los em primeiro lugar. Ao mesmo tempo, você não gostaria que seu package se tornasse muito grande: você atingirá todos os problemas que estamos tentando resolver aqui, apenas dentro desse package. Aqui estão alguns limites que eu costumo usar: * "design system" type das coisas como buttons, modal dialogs, layouts, tooltips, etc., todos devem ser packages * features em alguns limites de IU "naturais" são bons candidatos para um package - ou seja, algo que vive em um modal dialog, em uma drawer, em um slide-in panel, etc. * features "compartilháveis" - aquelas que podem ser usadas ​​em vários lugares * algo que você pode descrever como uma "feature" isolada com limites claros, lógicos e idealmente visíveis na IU Além disso, assim como no artigo anterior sobre como dividir o código em components, é muito importante que um package seja responsável apenas por uma coisa conceitual. Um package que exporta um Button, CreateIssueDialog e DateTimeConverter faz muitas coisas ao mesmo tempo e precisa ser "split up". ### Como organizar packages Embora seja possível criar apenas uma lista simples de todos os packages, e para certos tipos de projetos isso funcionaria, para produtos grandes e pesados ​​de UI provavelmente não será o suficiente. Ver algo como packages "tooltip" e "settings page" juntos me faz estremecer 😖. Ou pior - se você tiver packages "backend" e "frontend" juntos. Isso não é apenas confuso, mas também perigoso: a última coisa que você quer é acidentalmente pull algum código "backend" para seu frontend bundle. A estrutura real do repositório dependeria muito do que exatamente é o produto que você está implementando (ou mesmo quantos produtos existem), se você tem backend ou apenas frontend, e provavelmente mudará e evoluirá significativamente ao longo do tempo. Felizmente, esta é a grande vantagem dos packages: a estrutura real é completamente independente do código, você pode drag-and-drop eles e reestruturá-los uma vez por semana sem quaisquer consequências se houver necessidade. Considerando que o custo do "erro" na estrutura é bastante baixo, não há necessidade de pensar demais, pelo menos no início. Se o seu projeto for somente frontend, você pode até começar com uma lista simples: /packages /button ... /footer /settings ... e evoluir ao longo do tempo para algo como isto: /packages /core /button /modal /tooltip ... /product-one /footer /settings ... /product-two ... Ou, se você tiver um backend, poderia ser algo assim: /packages /frontend ... // o mesmo que acima /backend ... // alguns backend packages específicos /common ... // alguns packages que são que são conpartilhados entre frontend e backend Onde em "common" você colocaria algum código que fosse compartilhado entre frontend e backend. Normalmente serão algumas configs, constants, utils do tipo lodash, types compartilhados. ## Como estruturar um package em si Para resumir a grande seção acima: "use monorepo, extraia features em packages". 🙂 Agora para a próxima parte - como organizar o package em si. Três coisas são importantes aqui para mim: naming convention, separar o package em layers distintas e hierarquia estrita. ### Naming convention Todo mundo adora nomear coisas e debater sobre o quão ruins são as outras coisas, não é? Para reduzir o tempo gasto em intermináveis ​​​​tópicos de comentários do GitHub e "calm down poor geeks" com TOC relacionado ao código como eu, é melhor concordar com uma "convenção de nomenclatura" de uma vez para todos. Qual usar não importa muito na minha opinião, desde que seja seguido consistentemente ao longo do projeto. Se você tem `ReactFeatureHere.ts` e `react-feature-here.ts` no mesmo repositório, um gatinho chora em algum lugar 😿. Eu costumo usar este: /my-feature-name /assets // se eu tiver algumas imagens, elas vão para um folder próprio logo.svg index.tsx // código principal da feature test.tsx // tests para a feature se necessário stories.tsx // stories para storybooks se eu usar elas styles.(tsx|scss) // Gosto de separar os styles da lógica dos components types.ts // se os types forem compartilhados entre diferentes arquivos dentro da feature utils.ts // utils muito simples que são usados ​​*apenas* nesta feature hooks.tsx // pequenas hooks que eu uso *somente* nesta feature Se uma feature tiver alguns components menores imported diretamente para `index.tsx`, eles ficarão assim: /my-feature-name ... // o mesmo que antes header.tsx header.test.tsx header.styles.tsx ... // etc ou, mais provavelmente, eu extrairia eles imediatamente para folders e eles ficariam assim: /my-feature-name ... // index o mesmo de antes /header index.tsx ... // etc, exatamente o mesmo naming aqui /footer index.tsx ... // etc, exatamente o mesmo naming aqui A abordagem de folders é muito mais otimizada para desenvolvimento orientado a copiar e colar 😊: ao criar uma nova feature copiando e colando a estrutura da feature próxima, tudo o que você precisa fazer é renomear apenas um folder. Todos os arquivos serão nomeados exatamente da mesma forma. Além disso, é mais fácil criar um modelo mental do package, refatorar e mover o código (sobre isso na próxima seção). ### Layers dentro de um package Um package típico com uma feature complicada teria algumas "layers" distintas: pelo menos a layer "UI" e a layer "Data". Embora seja provavelmente possível misturar tudo, eu ainda recomendaria não fazer isso: renderizar buttons e buscar dados do backend são preocupações muito diferentes. Separá-los dará ao package mais estrutura e previsibilidade. E para que o projeto permaneça relativamente saudável em termos de arquitetura e código, o crucial é ser capaz de identificar claramente as layers que são importantes para seu app, mapear o relacionamento entre elas e organizar tudo isso de uma forma que esteja alinhada com quaisquer ferramentas e frameworks usados. Se eu estivesse implementando um projeto React do zero hoje, com Graphql para manipulações de dados e React state puro para gerenciamento de state (ou seja, sem Redux ou qualquer outra biblioteca), eu teria as seguintes layers: * "Data" layer - queries, mutations e outras coisas que são responsáveis ​​por conectar-se às fontes de dados externas e transformá-las. Usado apenas pela UI layer, não depende de nenhuma outra layer. * "Shared" layer - vários utils, functions, hooks, mini-components, types e constants que são usados ​​em todo o package por todas as outras layers. Não depende de nenhuma outra layer. * "ui" layer - a implementação real da feature. Depende das layers "data" e "shared", ninguém depende dela. É isso! Se eu estivesse usando alguma biblioteca externa de gerenciamento de state, provavelmente adicionaria a layer "state" também. Essa provavelmente seria uma ponte entre "data" e "ui", e portanto usaria as layers "shared" e "data" e "UI" usaria "state" em vez de "data". E do ponto de vista dos detalhes da implementação, todas as layers são top-level folders em um package: /my-feature-package /shared /ui /data index.ts package.json Com cada "layer" usando a mesma naming convention descrita acima. Então sua "data" layer ficaria mais ou menos assim: /data index.ts get-some-data.ts get-some-data.test.ts update-some-data.ts update-some-data.test.ts Para packages mais complicados, eu poderia split essas layers, preservando seu propósito e suas características. A layer "Data" poderia ser split em "queries" ("getters") e "mutations" ("setters"), por exemplo, e essas podem permanecer no folder "data" ou subir: /my-feature-package /shared /ui /queries /mutations index.ts package.json Ou você pode extrair algumas sub-layers da "shared" layer, como "types" e "UI components compartilhados" (o que transformaria instantaneamente essa sub-layer no tipo "IU" a propósito, já que ninguém além de "IU" pode usar UI components). /my-feature-package /shared-ui /ui /queries /mutations /types index.ts package.json Contanto que você consiga definir claramente qual é o propósito de cada "sublayer", saber qual "sublayer" pertence a qual "layer" e conseguir visualizar e explicar isso para todos na equipe - tudo funciona! ### Hierarquia rigorosa dentro das layers A peça final do quebra-cabeça, que torna essa arquitetura previsível e sustentável, é uma hierarquia rigorosa dentro das layers. Isso será especialmente visível na IU layer, já que em React apps ela geralmente é a mais complicada. Vamos começar, por exemplo, a estruturar uma página simples, com um header e um footer. Teríamos o arquivo "index.ts" - o arquivo principal, onde a página se reúne, e os components "header.ts" e "footer.ts". /my-page index.ts header.ts footer.ts Agora, todos eles terão seus próprios components que eu gostaria de colocar em seus próprios arquivos. "Header", por exemplo, terá os components "Search bar" e "Send feedback". Na maneira flat "tradicional" de organizar apps, nós colocaríamos eles um ao lado do outro, não é? Seria algo assim: /my-page index.ts header.ts footer.ts search-bar.ts send-feedback.ts E então, se eu quiser adicionar o mesmo button "send-feedback" ao footer component, eu novamente faria import dele para "footer.ts" de "send-feedback.ts", certo? Afinal, ele está próximo e parece natural. Infelizmente, o que aconteceu foi que violamos os limites entre nossas layers ("UI" e "shared") sem nem perceber. Se eu continuasse adicionando mais e mais components a essa estrutura flat, e provavelmente continuarei, applications reais tendem a ser bem complicadas, provavelmente violarei elas mais algumas vezes. Isso transformará esse folder em sua própria "bola de lama", onde é completamente imprevisível qual component depende de qual. E, como resultado, desembaraçar tudo isso e extrair algo desse folder, quando chegar a hora da refatoração, pode se tornar um exercício muito complicado. Em vez disso, podemos estruturar essa layer de forma hierárquica. As regras são: * apenas os arquivos principais (por exemplo, "index.ts") em uma pasta podem ter subcomponents (sub-modules) e podem import eles * você pode import apenas dos "children", não dos "vizinhos" * você não pode pular um level e pode import apenas dos children diretos Ou, se preferir visual, é apenas uma árvore: E se você precisar compartilhar algum código entre diferentes levels dessa hierarquia (como nosso send-feedback component), você verá instantaneamente que está violando as regras da hierarquia, já que onde quer que você coloque ele, você terá que import ele dos parents ou dos vizinhos. Então, em vez disso, ele seria extraído para a layer "shared" e imported de lá. Ficaria assim: /my-page /shared send-feedback.ts /ui index.ts /header index.ts search-bar.ts /footer index.ts Dessa forma, a IU layer (ou qualquer layer onde essa regra se aplica) se transforma em uma estrutura de árvore, onde cada branch é independente de qualquer outro branch. Extrair qualquer coisa desse package agora é moleza: tudo o que você precisa fazer é drag e drop um folder em um novo lugar. E você sabe com certeza que nenhum component na árvore da IU será afetado por ela, exceto aquele que realmente usa ele. A única coisa com a qual você pode precisar lidar adicionalmente é a "shared" layer. O app completo com a data layer ficaria assim: Algumas layers claramente definidas, completamente encapsuladas e previsíveis. /my-page /shared send-feedback.ts /data get-something.ts send-something.ts /ui index.ts /header index.ts search-bar.ts /footer index.ts ### React recomenda não aninhar Se você ler a documentação do React sobre a estrutura de projeto recomendada, verá que React realmente recomenda não aninhar muito. A recomendação oficial é "considere limitar-se a um máximo de três ou quatro pastas aninhadas dentro de um único projeto". E essa recomendação é muito relevante para essa abordagem também: se seu package ficar muito aninhado, é um sinal claro de que você pode precisar pensar em splitting ele em packages menores. 3-4 níveis de aninhamento, na minha experiência, são suficientes até mesmo para features muito complicadas. A beleza da arquitetura de packages, porém, é que você pode organizar seus packages com tanto aninhamento quanto precisar sem ficar preso a essa restrição - você nunca se refere a outro package por seu relative path, apenas por seu nome. Um package com o nome `@project/change-setting-dialog` que reside no path `packages/change-settings-dialog` ou está oculto dentro de `/packages/product/features/settings-page/change-setting-dialog`, será chamado de `@project/change-setting-dialog` independentemente de sua localização física. ### Ferramenta de gerenciamento Monorepo É impossível falar sobre multi-packages monorepo para sua arquitetura sem tocar pelo menos um pouco nas ferramentas de gerenciamento monorepo. O maior problema geralmente é o gerenciamento de dependências dentro dele. Imagine se alguns dos seus monorepo packages usam uma dependência externa, `lodash` por exemplo. /my-feature-one package.json // este usa lodash@3.4.5 /my-other-feature package.json // este usa lodash@3.4.5 Agora o lodash lança uma nova versão, `lodash@4.0.0`, e você quer mover seu projeto para ele. Você precisaria atualizá-lo em todos os lugares ao mesmo tempo: a última coisa que você quer é que alguns dos packages permaneçam na versão antiga, enquanto outros usem a nova. Se você estiver no `npm` ou no antigo `yarn`, isso seria um desastre: eles instalariam várias cópias (não duas, várias) do `lodash` em seu sistema, o que resultaria em tempos maiores de instalação e build, e seus tamanhos de bundles disparando. Sem mencionar a diversão de desenvolver uma nova feature quando você está usando duas versões diferentes da mesma biblioteca em todo o projeto. Não vou tocar no que usar se seu projeto for publicado no npm e de código aberto: provavelmente algo como Lerna seria o suficiente, mas esse é um tópico completamente diferente. Se, no entanto, seu repositório for **private** , as coisas estão ficando mais interessantes. Porque tudo o que você realmente precisa para que essa arquitetura funcione é packages "aliasing", nada mais. Ou seja, apenas links simbólicos básicos que tanto o Yarn quanto o Npm fornecem por meio da ideia de workspaces. Parece com isso. Você tem o arquivo "root" `package.json`, onde você declara onde estão os workspaces (ou seja, seus packages locais): { "private": true, "workspaces": ["packages/**"] } E então, da próxima vez que você executar `yarn install`, todos os packages do folder packages se tornarão packages "adequados" e estarão disponíveis no seu projeto por meio dos name deles. Esse é todo o monorepo setup! Quanto às dependências. O que acontecerá se você tiver a mesma dependência em alguns packages? /packages /my-feature-one package.json // este usa lodash@3.4.5 /my-other-feature package.json // este usa lodash@3.4.5 Quando você executa `yarn install`, ele irá "hoist" (içar) esse package para o root `node_modules`: /node_modules lodash@3.4.5 /packages /my-feature-one package.json // este usa lodash@3.4.5 /my-other-feature package.json // este usa lodash@3.4.5 Esta é exatamente a mesma situação que se você declarasse `lodash@3.4.5` apenas no root `package.json`. O que estou dizendo é, e provavelmente serei enterrado vivo pelos puristas da internet por isso, incluindo eu mesmo há dois anos: você não precisa declarar nenhuma das dependências em seus packages locais. Tudo pode ir para o root `package.json`. E seus arquivos `package.json` em packages locais serão apenas arquivos `json` muito leves, que especificam apenas os fields "name" e "main". Configuração muito mais fácil de gerenciar, especialmente se você estiver apenas começando. ## Estrutura do projeto React para scale: visão geral final Huh, isso foi muito texto. E mesmo isso é apenas uma breve visão geral: muitas outras coisas podem ser ditas sobre o tópico! Vamos recapitular o que já foi dito, pelo menos: **Decomposition** é a chave para scale com sucesso seu React app. Pense no seu projeto não como um "projeto" monolítico, mas como uma combinação de "features" independentes do tipo caixa-preta com sua própria API pública para os consumidores usarem. A mesma discussão de "monólito" vs "microservices" realmente. **Arquitetura Monorepo** é perfeita para isso. Extraia suas features em packages; organize seus packages da maneira que melhor funciona para seu projeto. **Layers** dentro de um package são importantes para dar a ele alguma estrutura. Você provavelmente terá pelo menos uma "data" layer, uma "UI" layer e uma "shared" layer. Pode introduzir mais, dependendo de suas necessidades, só precisa ter limites claros entre elas. **Estrutura hierárquica** de um package é legal. Ela facilita a refatoração, força você a ter limites mais claros entre as layers e força você a dividir seu package em outros menores quando ele se torna muito grande. **Gerenciamento de dependências** em um monorepo é um tópico complicado, mas se seu projeto for private, você não precisa se preocupar com isso. Apenas declare todas as suas dependências no root package.json e mantenha todos os packages locais livres delas. Você pode dar uma olhada na implementação dessa arquitetura neste repositório de exemplo: https://github.com/developerway/example-react-project. Este é apenas um exemplo básico para demonstrar os princípios descritos no artigo, então não se assuste com packages pequenos com apenas um `index.ts`: em um app real, eles serão muito maiores. Isso é tudo por hoje. Espero que você consiga aplicar alguns desses princípios (ou até mesmo todos eles!) aos seus apps e veja melhorias no seu desenvolvimento diário imediatamente! ✌🏼 ## Fonte Artigo escrito por **Nadia Makarevich**.
0 0 0 0
Preview
3 anos, 1 mês e 16 dias Esse foi o tempo que eu levei pra chegar ao meu primeiro cargo de liderança técnica na área de tecnologia. Eu não sou o sênior de 2 anos, muito menos o tech lead de 3 anos, pelo contrário eu comecei essa jornada antes mesmo de pensar em trabalhar em tecnologia, comecei essa jornada lá pelos idos de 2007 quando fui "líder" de alguns peões num estaleiro, líder entre aspas pois não tinha experiência e conhecimento nenhum sobre como liderar, orientar e inspirar alguém, eu quem fui orientado, liderado e inspirado por esses peões, carrego comigo para sempre tudo o que esses caras, pacientemente, compartilharam comigo. Amarilzo, Benites, Seu Bahia, Bidu, Di Menor, Henrique, Marreta, Azul, Seu Quadros(que tive o prazer de encontrar recentemente numa padaria aqui perto de casa) entre outros, que muito provavelmente jamais saberão o impacto que deixaram na minha vida profissional. Quando sai do estaleiro, fui alçado ao cargo de inspetor, é um nome e um cargo imponente, mas na verdade só significa que você é o responsável por todas as merdas que aparecerem ao longo da sua "inspeção". e aqui eu passei 12 anos da minha vida e lidei com dezenas, centenas de problemas diferentes que a área de petróleo e gás pode oferecer. De gente brigando de faca à bordo à vazamento de óleo na água. E muitas vezes eu estava sozinho, no meio do oceano, sem comunicação, eu tinha que decidir ali na hora o que fazer, como fazer e porque fazer. Uma vez um representante do cliente me encurralou numa sala e disse a celebre frase: "Eu sou cliente, você tem que fazer o que eu mando ou eu vou enfiar o custo de 12 horas de um navio parado na sua empresa." E eu respondi: "Eu represento uma inspetora independente, nós seguimos as normas internacionais XYZ, e pelas normas não podemos fazer dessa forma, se você quiser que façamos diferente preciso de uma autorização expressa da minha empresa via e-mail para ser anexada aos documentos." Obviamente me preparei para a demissão, mas eu estava correto e meu chefe direto me apoiou. Histórias como essa eu tenho várias, poderia ficar 8 horas num bar, bebendo, só contanto minhas histórias de bordo. Tudo isso me deu casca para um belo dia eu simplesmente pedir demissão de um lugar onde eu era o braço direito do dono e tentar a "sorte" em TI, sorte entre aspas porque eu sabia que eu poderia lidar com qualquer tipo de problema, meu maior medo era a parte técnica. Mas então, um gerente de TI chamado Denis me disse em uma reunião, onde eu estava sendo direcionado para uma equipe, que "a parte técnica você aprende" e de fato eu aprendi, não por mágica, sorte ou qualquer outra superstição mas sim com muita dedicação, desespero e esforço. Em muitos momentos pensei em voltar para a minha antiga área, pensei que não daria conta, pensei que estava entregando pouco, pensei, pensei, pensei... Na verdade era só medo do desconhecido e medo a gente encara e supera. Esses dois últimos parágrafos são um lembrete pro meu eu de amanhã que vai surtar, se desesperar e achar que não dá conta. Então eu não sou um líder técnico de 3 anos, eu sou um profissional capaz de liderar tecnicamente uma equipe que foi forjado ao longo de 17 anos.
0 0 0 0
Preview
3 anos, 1 mês e 16 dias Esse foi o tempo que eu levei pra chegar ao meu primeiro cargo de liderança técnica na área de tecnologia. Eu não sou o sênior de 2 anos, muito menos o tech lead de 3 anos, pelo contrário eu comecei essa jornada antes mesmo de pensar em trabalhar em tecnologia, comecei essa jornada lá pelos idos de 2007 quando fui "líder" de alguns peões num estaleiro, líder entre aspas pois não tinha experiência e conhecimento nenhum sobre como liderar, orientar e inspirar alguém, eu quem fui orientado, liderado e inspirado por esses peões, carrego comigo para sempre tudo o que esses caras, pacientemente, compartilharam comigo. Amarilzo, Benites, Seu Bahia, Bidu, Di Menor, Henrique, Marreta, Azul, Seu Quadros(que tive o prazer de encontrar recentemente numa padaria aqui perto de casa) entre outros, que muito provavelmente jamais saberão o impacto que deixaram na minha vida profissional. Quando sai do estaleiro, fui alçado ao cargo de inspetor, é um nome e um cargo imponente, mas na verdade só significa que você é o responsável por todas as merdas que aparecerem ao longo da sua "inspeção". e aqui eu passei 12 anos da minha vida e lidei com dezenas, centenas de problemas diferentes que a área de petróleo e gás pode oferecer. De gente brigando de faca à bordo à vazamento de óleo na água. E muitas vezes eu estava sozinho, no meio do oceano, sem comunicação, eu tinha que decidir ali na hora o que fazer, como fazer e porque fazer. Uma vez um representante do cliente me encurralou numa sala e disse a celebre frase: "Eu sou cliente, você tem que fazer o que eu mando ou eu vou enfiar o custo de 12 horas de um navio parado na sua empresa." E eu respondi: "Eu represento uma inspetora independente, nós seguimos as normas internacionais XYZ, e pelas normas não podemos fazer dessa forma, se você quiser que façamos diferente preciso de uma autorização expressa da minha empresa via e-mail para ser anexada aos documentos." Obviamente me preparei para a demissão, mas eu estava correto e meu chefe direto me apoiou. Histórias como essa eu tenho várias, poderia ficar 8 horas num bar, bebendo, só contanto minhas histórias de bordo. Tudo isso me deu casca para um belo dia eu simplesmente pedir demissão de um lugar onde eu era o braço direito do dono e tentar a "sorte" em TI, sorte entre aspas porque eu sabia que eu poderia lidar com qualquer tipo de problema, meu maior medo era a parte técnica. Mas então, um gerente de TI chamado Denis me disse em uma reunião, onde eu estava sendo direcionado para uma equipe, que "a parte técnica você aprende" e de fato eu aprendi, não por mágica, sorte ou qualquer outra superstição mas sim com muita dedicação, desespero e esforço. Em muitos momentos pensei em voltar para a minha antiga área, pensei que não daria conta, pensei que estava entregando pouco, pensei, pensei, pensei... Na verdade era só medo do desconhecido e medo a gente encara e supera. Esses dois últimos parágrafos são um lembrete pro meu eu de amanhã que vai surtar, se desesperar e achar que não dá conta. Então eu não sou um líder técnico de 3 anos, eu sou um profissional capaz de liderar tecnicamente uma equipe que foi forjado ao longo de 17 anos.
0 0 0 0
Preview
C# Lowering ## O processo de compilação Você sabe como é o processo que seu código em C# passa para ser executado? Muita se fala sobre o processo de compilar o código, que transforma o código C# em IL, mas existe um passo anterior que transforma C# em ... C#. Esse processo é conhecido como **_Lowering_**. Ele é responsável por um processo que vai _"otimizar"_ o código para o compilador e vai trocar algumas facilidades da linguagem por comandos que são entendidos mais facilmente pelo compilador. Isso tem ajudado muito na adição de funcionalidades no C#, como é o caso do _record_ ou até mesmo como agora são permitidos _top-level statements_. ## Exemplos de Lowering O record é utilizado para representar um objeto de valores imutáveis. Porém isso já poderia ser feito usando classes com parâmetros no construtor e propriedades _readonly_. O que o record faz é _facilitar_ essa implementação comum para o desenvolvedor. Por exemplo, quando você escreve: public record Record(int ID); O que o compilador vai receber é: [NullableContext(1)] [Nullable(0)] public class Record : IEquatable<Record> { [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly int <ID>k__BackingField; [CompilerGenerated] protected virtual Type EqualityContract { [CompilerGenerated] get { return typeof(Record); } } public int ID { [CompilerGenerated] get { return <ID>k__BackingField; } [CompilerGenerated] init { <ID>k__BackingField = value; } } public Record(int ID) { <ID>k__BackingField = ID; base..ctor(); } [CompilerGenerated] public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Record"); stringBuilder.Append(" { "); if (PrintMembers(stringBuilder)) { stringBuilder.Append(' '); } stringBuilder.Append('}'); return stringBuilder.ToString(); } [CompilerGenerated] protected virtual bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("ID = "); builder.Append(ID.ToString()); return true; } [NullableContext(2)] [CompilerGenerated] public static bool operator !=(Record left, Record right) { return !(left == right); } [NullableContext(2)] [CompilerGenerated] public static bool operator ==(Record left, Record right) { return (object)left == right || ((object)left != null && left.Equals(right)); } [CompilerGenerated] public override int GetHashCode() { return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(<ID>k__BackingField); } [NullableContext(2)] [CompilerGenerated] public override bool Equals(object obj) { return Equals(obj as Record); } [NullableContext(2)] [CompilerGenerated] public virtual bool Equals(Record other) { return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(<ID>k__BackingField, other.<ID>k__BackingField)); } [CompilerGenerated] public virtual Record <Clone>$() { return new Record(this); } [CompilerGenerated] protected Record(Record original) { <ID>k__BackingField = original.<ID>k__BackingField; } [CompilerGenerated] public void Deconstruct(out int ID) { ID = this.ID; } } Muito mais complexo, não é? Outro exemplo, que está na linguagem tem um bom tempo é o uso de _foreach_ : Record[] records = [new Record(1), new Record(2), new Record(3)]; foreach (var x in records) { Console.WriteLine(x.ID); } Se torna: private static void <Main>$(string[] args) { Record[] array = new Record[3]; array[0] = new Record(1); array[1] = new Record(2); array[2] = new Record(3); Record[] array2 = array; Record[] array3 = array2; int num = 0; while (num < array3.Length) { Record record = array3[num]; Console.WriteLine(record.ID); num++; } } No exemplo ainda conseguimos ver como a inicialização de listas, que foi adicionada mais recentemente, funciona por trás dos panos. Outro ponto importante é que nesse caso podemos ver a diferença de código gerado quando se usa tipos diferentes. O mesmo código anterior, usando _List_ ao invés de _Array_ gera a seguinte saída: int num = 3; List<Record> list = new List<Record>(num); CollectionsMarshal.SetCount(list, num); Span<Record> span = CollectionsMarshal.AsSpan(list); int num2 = 0; span[num2] = new Record(1); num2++; span[num2] = new Record(2); num2++; span[num2] = new Record(3); num2++; List<Record> list2 = list; List<Record>.Enumerator enumerator = list2.GetEnumerator(); try { while (enumerator.MoveNext()) { Record current = enumerator.Current; Console.WriteLine(current.ID); } } finally { ((IDisposable)enumerator).Dispose(); } Tudo isso permitiu que várias facilitações e funcionalidades fossem feitas na linguagem ao longo dos anos, sem alterações muito significativas no compilador. Acredito que uma das maiores foi o acréscimo de _async/await_ na linguagem, que facilitou muito a programação assíncrona em comparação com o modelo anterior. Além dessas facilidades de escrita e funcionalidades, também são implementadas melhorias de performance. Saber que esse processo existe, e como ver o código gerado por ele, pode facilitar o nosso entendimento de alguns comportamentos que nosso código exibe, como podemos ver a seguir. ## Comportamentos inesperados O que me motivou esse post foi encontrar recentemente um comportamento inesperado em um método extremamente simples e que foi compreendido com mais facilidade através da análise do código gerado pelo _lowering_. Vamos levar a seguinte classe em consideração: public class Example { public double ValueA {get;set;} public int ValueB {get;set;} public object GetByIndex(int index) => index switch { 0 => ValueA, 1 => ValueB, _ => throw new Exception("") }; } Essa implementação foi necessária por conta de um tipo de serialização que leva em consideração a ordem dos campos, e foi levemente alterada para simplificar o exemplo. Como esse código é usado por uma biblioteca de serialização o tipo do valor retornado é bem importante (mesmo que esteja sofrendo _boxing_ pelo tipo de retorno ser _object_). Agora a dúvida. Caso o seguinte código seja executado, o que será exibido no console? var example = new Example { ValueA = 2.0, ValueB = 50 }; Console.WriteLine(example.GetByIndex(0).GetType().FullName); Console.WriteLine(example.GetByIndex(1).GetType().FullName); Se você espera que seja > System.Double > System.Int32 Eu e você estamos no mesmo barco. Porém no barco errado.... O que é exibido é: > System.Double > System.Double Agora vamos entender porque isso acontece, olhando o código gerado após o lowering para o método GetByIndex: [NullableContext(1)] public object GetByIndex(int index) { double num; if (index != 0) { if (index != 1) { throw new Exception(""); } num = ValueB; } else { num = ValueA; } return num; } Olhando esse código fica claro o motivo de o valor retornado está com o tipo _double_ , agora falta entender o porquê. Primeiro vamos olhar outros cenários para entender quando isso acontece e quando não acontece. Quando usamos um _switch...case_ ao invés de um _switch expression_ o resultado é o esperado. Antes do lowering: public object GetByIndex(int index) { switch(index) { case 0: return ValueA; case 1: return ValueB; default: throw new Exception(""); } } Após lowering: [NullableContext(1)] public object GetByIndex(int index) { if (index != 0) { if (index == 1) { return ValueB; } throw new Exception(""); } return ValueA; } O problema também não acontece caso os tipos das propriedades sejam _double_ e _decimal_ : public class Example { public double ValueA {get;set;} public decimal ValueB {get;set;} public object GetByIndex(int index) => index switch { 0 => ValueA, 1 => ValueB, _ => throw new Exception("") }; } Após o lowering: [NullableContext(1)] public object GetByIndex(int index) { if (index != 0) { if (index == 1) { return ValueB; } throw new Exception(""); } return ValueA; } Então o que acarreta na resultado inesperado? Se olharmos o _switch expression_ inicial, fora do contexto de implementação do método, fica um pouco mais fácil de entender. int index = 1; var result = index switch { 0 => 2.0, 1 => 1, _ => throw new Exception("") }; Como podemos ver, o resultado da _switch expression_ deve ser atribuído a uma variável e para isso o compilador escolhe um tipo em comum entre os retornos de todos as opções existentes. Como qualquer valor do tipo _int_ pode ser representado no tipo _double_ , o valor inteiro é **convertido** para double. Caso adicionemos uma nova opção `2 => "outro valor"`, então o único tipo em comum entre as três opções é _object_. Desse modo os três valores vão sofrer **boxing** , e mantêm o tipo original de alguma forma. ## O que posso fazer? Conhecer a existência do processo de _lowering_ e como ele se encaixa como etapa do processo de compilação nos ajuda a ter uma caixa de ferramentas maior na hora de entender e solucionar problemas. Quer ver como partes do seu código está ficando após o lowering? Pode acessar aqui e ver: https://sharplab.io/ Além disso, se quiser ver um pouco mais:
0 0 0 0
Preview
C# Lowering ## O processo de compilação Você sabe como é o processo que seu código em C# passa para ser executado? Muita se fala sobre o processo de compilar o código, que transforma o código C# em IL, mas existe um passo anterior que transforma C# em ... C#. Esse processo é conhecido como **_Lowering_**. Ele é responsável por um processo que vai _"otimizar"_ o código para o compilador e vai trocar algumas facilidades da linguagem por comandos que são entendidos mais facilmente pelo compilador. Isso tem ajudado muito na adição de funcionalidades no C#, como é o caso do _record_ ou até mesmo como agora são permitidos _top-level statements_. ## Exemplos de Lowering O record é utilizado para representar um objeto de valores imutáveis. Porém isso já poderia ser feito usando classes com parâmetros no construtor e propriedades _readonly_. O que o record faz é _facilitar_ essa implementação comum para o desenvolvedor. Por exemplo, quando você escreve: public record Record(int ID); O que o compilador vai receber é: [NullableContext(1)] [Nullable(0)] public class Record : IEquatable<Record> { [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly int <ID>k__BackingField; [CompilerGenerated] protected virtual Type EqualityContract { [CompilerGenerated] get { return typeof(Record); } } public int ID { [CompilerGenerated] get { return <ID>k__BackingField; } [CompilerGenerated] init { <ID>k__BackingField = value; } } public Record(int ID) { <ID>k__BackingField = ID; base..ctor(); } [CompilerGenerated] public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Record"); stringBuilder.Append(" { "); if (PrintMembers(stringBuilder)) { stringBuilder.Append(' '); } stringBuilder.Append('}'); return stringBuilder.ToString(); } [CompilerGenerated] protected virtual bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("ID = "); builder.Append(ID.ToString()); return true; } [NullableContext(2)] [CompilerGenerated] public static bool operator !=(Record left, Record right) { return !(left == right); } [NullableContext(2)] [CompilerGenerated] public static bool operator ==(Record left, Record right) { return (object)left == right || ((object)left != null && left.Equals(right)); } [CompilerGenerated] public override int GetHashCode() { return EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(<ID>k__BackingField); } [NullableContext(2)] [CompilerGenerated] public override bool Equals(object obj) { return Equals(obj as Record); } [NullableContext(2)] [CompilerGenerated] public virtual bool Equals(Record other) { return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(<ID>k__BackingField, other.<ID>k__BackingField)); } [CompilerGenerated] public virtual Record <Clone>$() { return new Record(this); } [CompilerGenerated] protected Record(Record original) { <ID>k__BackingField = original.<ID>k__BackingField; } [CompilerGenerated] public void Deconstruct(out int ID) { ID = this.ID; } } Muito mais complexo, não é? Outro exemplo, que está na linguagem tem um bom tempo é o uso de _foreach_ : Record[] records = [new Record(1), new Record(2), new Record(3)]; foreach (var x in records) { Console.WriteLine(x.ID); } Se torna: private static void <Main>$(string[] args) { Record[] array = new Record[3]; array[0] = new Record(1); array[1] = new Record(2); array[2] = new Record(3); Record[] array2 = array; Record[] array3 = array2; int num = 0; while (num < array3.Length) { Record record = array3[num]; Console.WriteLine(record.ID); num++; } } No exemplo ainda conseguimos ver como a inicialização de listas, que foi adicionada mais recentemente, funciona por trás dos panos. Outro ponto importante é que nesse caso podemos ver a diferença de código gerado quando se usa tipos diferentes. O mesmo código anterior, usando _List_ ao invés de _Array_ gera a seguinte saída: int num = 3; List<Record> list = new List<Record>(num); CollectionsMarshal.SetCount(list, num); Span<Record> span = CollectionsMarshal.AsSpan(list); int num2 = 0; span[num2] = new Record(1); num2++; span[num2] = new Record(2); num2++; span[num2] = new Record(3); num2++; List<Record> list2 = list; List<Record>.Enumerator enumerator = list2.GetEnumerator(); try { while (enumerator.MoveNext()) { Record current = enumerator.Current; Console.WriteLine(current.ID); } } finally { ((IDisposable)enumerator).Dispose(); } Tudo isso permitiu que várias facilitações e funcionalidades fossem feitas na linguagem ao longo dos anos, sem alterações muito significativas no compilador. Acredito que uma das maiores foi o acréscimo de _async/await_ na linguagem, que facilitou muito a programação assíncrona em comparação com o modelo anterior. Além dessas facilidades de escrita e funcionalidades, também são implementadas melhorias de performance. Saber que esse processo existe, e como ver o código gerado por ele, pode facilitar o nosso entendimento de alguns comportamentos que nosso código exibe, como podemos ver a seguir. ## Comportamentos inesperados O que me motivou esse post foi encontrar recentemente um comportamento inesperado em um método extremamente simples e que foi compreendido com mais facilidade através da análise do código gerado pelo _lowering_. Vamos levar a seguinte classe em consideração: public class Example { public double ValueA {get;set;} public int ValueB {get;set;} public object GetByIndex(int index) => index switch { 0 => ValueA, 1 => ValueB, _ => throw new Exception("") }; } Essa implementação foi necessária por conta de um tipo de serialização que leva em consideração a ordem dos campos, e foi levemente alterada para simplificar o exemplo. Como esse código é usado por uma biblioteca de serialização o tipo do valor retornado é bem importante (mesmo que esteja sofrendo _boxing_ pelo tipo de retorno ser _object_). Agora a dúvida. Caso o seguinte código seja executado, o que será exibido no console? var example = new Example { ValueA = 2.0, ValueB = 50 }; Console.WriteLine(example.GetByIndex(0).GetType().FullName); Console.WriteLine(example.GetByIndex(1).GetType().FullName); Se você espera que seja > System.Double > System.Int32 Eu e você estamos no mesmo barco. Porém no barco errado.... O que é exibido é: > System.Double > System.Double Agora vamos entender porque isso acontece, olhando o código gerado após o lowering para o método GetByIndex: [NullableContext(1)] public object GetByIndex(int index) { double num; if (index != 0) { if (index != 1) { throw new Exception(""); } num = ValueB; } else { num = ValueA; } return num; } Olhando esse código fica claro o motivo de o valor retornado está com o tipo _double_ , agora falta entender o porquê. Primeiro vamos olhar outros cenários para entender quando isso acontece e quando não acontece. Quando usamos um _switch...case_ ao invés de um _switch expression_ o resultado é o esperado. Antes do lowering: public object GetByIndex(int index) { switch(index) { case 0: return ValueA; case 1: return ValueB; default: throw new Exception(""); } } Após lowering: [NullableContext(1)] public object GetByIndex(int index) { if (index != 0) { if (index == 1) { return ValueB; } throw new Exception(""); } return ValueA; } O problema também não acontece caso os tipos das propriedades sejam _double_ e _decimal_ : public class Example { public double ValueA {get;set;} public decimal ValueB {get;set;} public object GetByIndex(int index) => index switch { 0 => ValueA, 1 => ValueB, _ => throw new Exception("") }; } Após o lowering: [NullableContext(1)] public object GetByIndex(int index) { if (index != 0) { if (index == 1) { return ValueB; } throw new Exception(""); } return ValueA; } Então o que acarreta na resultado inesperado? Se olharmos o _switch expression_ inicial, fora do contexto de implementação do método, fica um pouco mais fácil de entender. int index = 1; var result = index switch { 0 => 2.0, 1 => 1, _ => throw new Exception("") }; Como podemos ver, o resultado da _switch expression_ deve ser atribuído a uma variável e para isso o compilador escolhe um tipo em comum entre os retornos de todos as opções existentes. Como qualquer valor do tipo _int_ pode ser representado no tipo _double_ , o valor inteiro é **convertido** para double. Caso adicionemos uma nova opção `2 => "outro valor"`, então o único tipo em comum entre as três opções é _object_. Desse modo os três valores vão sofrer **boxing** , e mantêm o tipo original de alguma forma. ## O que posso fazer? Conhecer a existência do processo de _lowering_ e como ele se encaixa como etapa do processo de compilação nos ajuda a ter uma caixa de ferramentas maior na hora de entender e solucionar problemas. Quer ver como partes do seu código está ficando após o lowering? Pode acessar aqui e ver: https://sharplab.io/ Além disso, se quiser ver um pouco mais:
0 0 0 0
Preview
Visão Geral do Domain-Driven Design (DDD) ## Introdução ao Domain-Driven Design (DDD) **Domain-Driven Design (DDD)** é uma abordagem de projeto de software centrada no **domínio**. Não existe uma única forma "correta" de implementar DDD, pois não é um padrão rígido. Ao longo da minha carreira como Engenheiro de Software, vi diversas implementações, cada uma com vantagens e desvantagens. ## O que é DDD? DDD é uma metodologia para projetar software **modelando um domínio** a partir do conhecimento de especialistas, usando uma **linguagem ubíqua** e dividindo o sistema em **contextos delimitados**. Os principais objetivos do DDD são: 1. Focar no **domínio central** e na lógica de negócio; 2. Criar designs complexos baseados em **modelos do domínio** ; 3. Promover colaboração entre **especialistas técnicos e de negócio** para refinar modelos conceituais iterativamente. ## Domínio O **domínio** é a área de negócio ou contexto específico da aplicação. Modelar bem o domínio é crucial, pois ele é o **coração do software**. Um modelo mal definido pode fazer com que o sistema se comporte de forma imprevisível. ## Linguagem Ubíqua É uma **linguagem comum** entre desenvolvedores e especialistas de negócio. Termos como "Cliente" ou "Pedido" devem ser **consistentes** no código, documentação e discussões. Isso evita ambiguidades e garante que todos entendam o mesmo conceito. ## Contexto Delimitado Ao modelar um domínio, definimos **limites claros** entre responsabilidades. Por exemplo: * Quem é responsável por manter a relação entre duas entidades? * Onde regras de negócio específicas devem residir? Esses limites evitam conflitos e garantem que cada parte do sistema tenha **responsabilidade bem definida**. ## Agregado Um **agregado** é um grupo de objetos (entidades e objetos de valor) tratados como **uma única unidade**. Acesso a um agregado é feito por meio de sua **raiz de agregação** , que controla regras de negócio e ciclo de vida. **Importante** : Um agregado **não equivale a uma tabela no banco de dados**. Em domínios complexos, um agregado pode ser resultado de consultas elaboradas. ## Objeto de Valor São objetos **sem identidade única** , como `Endereço` ou `Número de Telefone`. Sua igualdade é definida por seus atributos, não por um ID. ## Entidade O oposto de um objeto de valor: possui **identidade única** (ID), como `Cliente` ou `Pedido`. ## Eventos de Domínio Eventos emitidos pelo **domínio ou raiz de agregação** quando ocorrem mudanças ou ações. Exemplo: DomainEventPublisher.Publish(new PedidoCriadoEvent(pedido.Id)); Em arquiteturas como **Event Sourcing** , esses eventos são armazenados ou publicados em filas. ## Repositórios Mecanismos para **abstrair o acesso ao banco de dados** , fornecendo métodos para buscar e persistir agregados. Exemplo: public interface IRepositorioCliente { Cliente ObterPorId(Guid id); void Salvar(Cliente cliente); } ## Serviços Encapsulam **lógica de negócio complexa ou processos** que não pertencem a uma entidade ou agregado. Devem ser **stateless** (sem estado). Exemplo: public class ServicoPagamento { public void ProcessarPagamento(Pedido pedido) { // Lógica de integração com gateway de pagamento } } ## Conclusão Apresentei os conceitos centrais do DDD. Para aprofundar, recomendo o livro **"Domain-Driven Design: Atacando as Complexidades no Coração do Software"** de Eric Evans. No próximo artigo, mostrarei exemplos práticos de código!
0 0 0 0
Preview
Visão Geral do Domain-Driven Design (DDD) ## Introdução ao Domain-Driven Design (DDD) **Domain-Driven Design (DDD)** é uma abordagem de projeto de software centrada no **domínio**. Não existe uma única forma "correta" de implementar DDD, pois não é um padrão rígido. Ao longo da minha carreira como Engenheiro de Software, vi diversas implementações, cada uma com vantagens e desvantagens. ## O que é DDD? DDD é uma metodologia para projetar software **modelando um domínio** a partir do conhecimento de especialistas, usando uma **linguagem ubíqua** e dividindo o sistema em **contextos delimitados**. Os principais objetivos do DDD são: 1. Focar no **domínio central** e na lógica de negócio; 2. Criar designs complexos baseados em **modelos do domínio** ; 3. Promover colaboração entre **especialistas técnicos e de negócio** para refinar modelos conceituais iterativamente. ## Domínio O **domínio** é a área de negócio ou contexto específico da aplicação. Modelar bem o domínio é crucial, pois ele é o **coração do software**. Um modelo mal definido pode fazer com que o sistema se comporte de forma imprevisível. ## Linguagem Ubíqua É uma **linguagem comum** entre desenvolvedores e especialistas de negócio. Termos como "Cliente" ou "Pedido" devem ser **consistentes** no código, documentação e discussões. Isso evita ambiguidades e garante que todos entendam o mesmo conceito. ## Contexto Delimitado Ao modelar um domínio, definimos **limites claros** entre responsabilidades. Por exemplo: * Quem é responsável por manter a relação entre duas entidades? * Onde regras de negócio específicas devem residir? Esses limites evitam conflitos e garantem que cada parte do sistema tenha **responsabilidade bem definida**. ## Agregado Um **agregado** é um grupo de objetos (entidades e objetos de valor) tratados como **uma única unidade**. Acesso a um agregado é feito por meio de sua **raiz de agregação** , que controla regras de negócio e ciclo de vida. **Importante** : Um agregado **não equivale a uma tabela no banco de dados**. Em domínios complexos, um agregado pode ser resultado de consultas elaboradas. ## Objeto de Valor São objetos **sem identidade única** , como `Endereço` ou `Número de Telefone`. Sua igualdade é definida por seus atributos, não por um ID. ## Entidade O oposto de um objeto de valor: possui **identidade única** (ID), como `Cliente` ou `Pedido`. ## Eventos de Domínio Eventos emitidos pelo **domínio ou raiz de agregação** quando ocorrem mudanças ou ações. Exemplo: DomainEventPublisher.Publish(new PedidoCriadoEvent(pedido.Id)); Em arquiteturas como **Event Sourcing** , esses eventos são armazenados ou publicados em filas. ## Repositórios Mecanismos para **abstrair o acesso ao banco de dados** , fornecendo métodos para buscar e persistir agregados. Exemplo: public interface IRepositorioCliente { Cliente ObterPorId(Guid id); void Salvar(Cliente cliente); } ## Serviços Encapsulam **lógica de negócio complexa ou processos** que não pertencem a uma entidade ou agregado. Devem ser **stateless** (sem estado). Exemplo: public class ServicoPagamento { public void ProcessarPagamento(Pedido pedido) { // Lógica de integração com gateway de pagamento } } ## Conclusão Apresentei os conceitos centrais do DDD. Para aprofundar, recomendo o livro **"Domain-Driven Design: Atacando as Complexidades no Coração do Software"** de Eric Evans. No próximo artigo, mostrarei exemplos práticos de código!
0 0 0 0
Preview
🚀O que é a palavra-chave field no C# 13? Agora ficou mais fácil trabalhar com propriedades no C#! O C# 13 trouxe a palavra-chave field, que permite acessar diretamente o campo de suporte gerado pelo compilador. Isso significa menos código repetitivo e propriedades mais fáceis de manter. No artigo, mostramos o que é o field, quando usá-lo e como ele pode tornar seu código mais simples e eficiente. 👉 Continue lendo aqui: https://www.develop4us.com/post/o-que-%C3%A9-a-palavra-chave-field-no-c-13 📢 Aproveite para acessar nossa área de membros! Na Develop4Us, você encontra cursos exclusivos, conteúdos aprofundados e uma comunidade para aprender e crescer como desenvolvedor. Faça parte e acelere sua jornada na programação! 🚀 https://www.develop4us.com
0 0 0 0
Preview
Simplifique modais com os atributos experimentais "command" e "commandfor" Os modais são um padrão comum de UI utilizado para exibir informações importantes ou solicitar ações...

hoje lancei mais um post no dev to sobre os novos atributos experimentais do button chamados command e commandfor. São atributos que vão ajudar muito a ter uma fluidez com relação a abrir modais e popover

dev.to/brunoredes/s...

#html #modal #webdev #braziliandevs #bolhadev

2 2 0 0

@clintonrocha.bsky.social , no primeiro momento eu havia esquecido da tag #braziliandevs, mas logo logo corrigi 🙏

1 0 1 0

@jessilyneh.bsky.social e @jeffquesado.ulivre.dev
é uma boa usar #braziliandevs nos artigos do dev.to, tem mt gente que se guia por essa # para ler artigos nessa plataforma :D

8 1 1 0

Que eu saiba não, porém se for postar em português use a tag de minha criação #braziliandevs

5 0 2 1