Nas últimas décadas passamos por uma série de mudanças que impactaram nosso dia-a-dia: a popularização dos computadores e celulares, melhorias na velocidade de internet, ascensão dos serviços digitais e por último mas não menos importante, o avanço das LLMS e a chegada extensiva de ferramentas de IA.
Em meio a tantas novidades, fica o questionamento: o que preciso fazer para continuar me destacando no mercado de tecnologia? Será que perderei meu emprego para as máquinas?
Evangelistas vs Resistência
Hoje a discussão é sobre a adoção ou não da IA e o medo de ser substituído, mas no começo dos anos 2000 a discussão era sobre usar ou não frameworks no processo de desenvolvimento de software: Os apoiadores defendiam a praticidade, a produtividade e o suporte, já os contrários defendiam a simplicidade, a manutenibilidade da propriedade intelectual e a segurança.
Para determinados contextos, essa discussão ainda existe, mas na grande maioria dos campos já é uma questão superada, a indústria já se adaptou para a utilização de frameworks e as principais linguagens de programação contam com frameworks robustos que são capazes de dar apoio para as equipes de desenvolvimento em praticamente todas as suas necessidades. Basta acompanhar os projetos como Spring, Laravel e Rails que podemos ver o empenho constante em trazer novidades e manter o processo de desenvolvimento em conformidade com as necessidades de mercado.
Essa foi só uma das discussões que tivemos, mas existiram várias outras, como:
- Frameworks de Aplicação (Anos 2000)
- JavaScript Puro vs Frameworks de Virtual DOM (Anos 2010)
- Cloud Computing vs On-Primise (Anos 2010)
- Utilização de IA no Processo de Desenvolvimento (Hoje)
O que permaneceu?
Certa vez estava ouvindo um podcast que falava sobre a longevidade de livros técnicos com o passar dos anos e o desafio de mantê-los atualizados e me deparei com a seguinte frase:
Se você quer ver se o seu livro ainda fará sentido 20 anos no futuro, tente encaixá-lo 20 anos no passado
(Peço perdão por não lembrar o autor da frase e não conseguir dar os devidos créditos).
Esse raciocínio é uma simples exemplificação do Efeito Lindy, teoria que determina que a expectativa de vida de algo não perecível é proporcional a sua idade atual. Se uma empresa existe há 10 anos, existe uma change muito grande dela continuar existindo por mais 10 anos, agora se uma ferramenta existe há 6 meses, não podemos prever muito o seu futuro para além de 6 meses.
Levando esse pensamento em consideração você deve se questionar:
Se eu vou ter mais 20, 30+ anos de carreira, o que ainda vai estar aqui nos próximos 30 anos?
Muitas ferramentas mudaram ao longo das últimas décadas mas a base nunca mudou, esses novos recursos não foram criados do absoluto zero, mas sim a partir da adaptação e melhoria sobre as tecnologias vigentes. Enquanto tivemos que nos adaptar a novas versões de frameworks, a ida para a cloud, trocas de IDEs e editores de texto, nossa preocupação se manteve a mesma (ou pelo menos deveria ser): resolver problemas por meio da tecnologia de maneira eficiente, seja com framework ou sem, seja na cloud ou on-premise, seja com IA ou não.
Os programas que estão instalados na sua máquina e que você utiliza para entregar suas tarefas podem até mudar, mas o coração da engenharia de software permanecerá o mesmo.
Habilidades Atemporais de Engenheiros de Software
A entrega de valor por meio de um código seguro e escalável está diretamente ligada a habilidades fundamentais que permanecem relevantes ao longo do tempo.
Essas habilidades se resumem em duas principais áreas:
- Domínio de Negócio
- Fundamentos Técnicos
Domínio de Negócio
É muito importante dominar as regras de negócio da área sua área de atuação, pois quanto mais profundo for o seu conhecimento sobre as iniciativas da sua empresa, mais simples tende a ser sua implementação, resultando em menos custos no processo de desenvolvimento.
Se você trabalha com Marketing, vá estudar sobre funil de vendas, privacidade de dados, analytics, sistemas de CRM.
Se você trabalha com pagamentos, vá estudar sobre como funciona o Pix, adquirentes, bandeiras de cartão, PCI DSS, antifraude.
Imagine o seguinte cenário: você foi responsável por criar um fluxo de pagamentos de cartão de crédito que se integra com uma API de PSP (Payment Service Provider) para processar as transações. Quando o pagamento for devidamente processado, você deve atualizar o status do pedido para refletir o comportamento em outras partes do sistema.
Sem muito conhecimento sobre a regra de negócio, você implementa a primeira versão:
@Service
public class PaymentService {
private final PSPClient pspClient;
PaymentService(PSPClient pspClient) {
this.pspClient = pspClient;
}
public void pay(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
PSPPaymentRequest request = new PSPPaymentRequest(amount);
PSPPaymentResponse response = pspClient.pay(request);
System.out.println("Payment successful. TransactionId: " + response.transactionId());
}
}
Embora este código funcione para exemplos iniciais, o cenário real de pagamentos com cartão exige o conceito de captura. Nesse fluxo, ao iniciar uma transação, o PSP primeiro realiza a autorização (reserva do valor no limite do cartão) sem efetivar a cobrança imediata.
É o que ocorre no e-commerce: o sistema valida o crédito antes de confirmar o pedido. Integrar esse estágio é fundamental para gerenciar fluxos posteriores, como chargebacks e estornos, que dependem obrigatoriamente da captura confirmada. Portanto, ajustaremos o código para separar os momentos de autorização e captura.
@Service
public class PaymentService {
private final PSPClient pspClient;
PaymentService(PSPClient pspClient) {
this.pspClient = pspClient;
}
public Payment pay(BigDecimal amount) {
validateAmount(amount);
Payment payment = new Payment(amount);
PSPPaymentResponse response = authorize(amount);
payment.authorize(response.transactionId());
pspClient.capture(payment.getPspTransactionId());
return payment;
}
private static void validateAmount(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
}
private PSPPaymentResponse authorize(BigDecimal amount) {
PSPPaymentRequest request = new PSPPaymentRequest(amount);
return pspClient.authorize(request);
}
}
Disclaimer: não utilize esse código em produção, ele foi desenvolvido com o único intuito de mostrar a diferença de implementações com o domínio sobre as regras de negócio.
Fundamentos Técnicos
Com o conhecimento de negócio adquirido, você deve ser capaz de identificar pontos importantes no ecossistema de trabalho e levantar perguntar importantes. Visando o exemplo anterior, vamos levar os seguintes pontos em consideração:
Se o usuário clicar em finalizar o pedido várias vezes, o que deveria impedir de gerar uma cobrança duplicada?
R.: Devemos trabalhar com o conceito de idempotência, garantindo do nosso lado que não ocorrerá duplicação de dados.
@Service
public class PaymentService {
private final PSPClient pspClient;
private final PaymentRepository paymentRepository;
PaymentService(PSPClient pspClient, PaymentRepository paymentRepository) {
this.pspClient = pspClient;
this.paymentRepository = paymentRepository;
}
public Payment pay(UUID idempotencyKey, BigDecimal amount) {
Optional<Payment> existing = paymentRepository.findByIdempotencyKey(idempotencyKey);
// não confie só no if, o idempotencyKey deve ser unique key
if (existing.isPresent()) {
return existing.get();
}
validateAmount(amount);
Payment payment = new Payment(amount, idempotencyKey);
PSPPaymentResponse response = authorize(idempotencyKey, amount);
payment.authorize(response.transactionId());
pspClient.capture(payment.getPspTransactionId());
return payment;
}
private static void validateAmount(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
}
private PSPPaymentResponse authorize(UUID idempotencyKey, BigDecimal amount) {
PSPPaymentRequest request = new PSPPaymentRequest(amount);
return pspClient.authorize(idempotencyKey, request);
}
}
Qual o tempo de latência do gateway de pagamento? E o tempo de resposta do meu servidor? Será que o cliente pode ficar tanto tempo pendurado? E se minha máquina cair ou travar?
R.: Nunca confie cegamente em um fornecedor externo, falhas vão acontecer e você não pode amarrar uma chamada http direto na chamada dele.
O ideal nesse cenário é salvar o pagamento com um status inicial e jogar para uma fila de processamento, liberando o cliente.
@Service
public class PaymentService {
private final PaymentPublisher publisher;
private final PaymentRepository repository;
PaymentService(PaymentRepository repository, PaymentPublisher publisher) {
this.repository = repository;
this.publisher = publisher;
}
public Payment pay(UUID idempotencyKey, BigDecimal amount) {
Optional<Payment> existing = repository.findByIdempotencyKey(idempotencyKey);
if (existing.isPresent()) {
return existing.get();
}
validateAmount(amount);
Payment payment = repository.save(new Payment(amount, idempotencyKey));
publisher.publish(new CreatePaymentEvent(payment.getId()));
return payment;
}
private static void validateAmount(BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Invalid amount");
}
}
}
@Service
@Transactional(rollbackFor = Exception.class)
class PaymentProcessorService {
private final PSPClient pspClient;
private final PaymentRepository paymentRepository;
PaymentProcessorService(PaymentRepository paymentRepository, PSPClient pspClient) {
this.paymentRepository = paymentRepository;
this.pspClient = pspClient;
}
public void handle(CreatePaymentEvent event) {
Payment payment = paymentRepository.findById(event.getPaymentId()).orElseThrow();
if (payment.getStatus() != PaymentStatus.PSP_CREATION_PENDING) {
return;
}
PSPPaymentResponse response = pspClient.authorize(payment.getIdempotencyKey(), new PSPPaymentRequest(payment.getAmount()));
payment.authorize(response.transactionId());
pspClient.capture(payment.getPspTransactionId());
}
}
O que acontece caso eu consiga criar o pedido no PSP mas não consiga capturá-lo? o que deveria acontecer? Em que ponto eu deveria recomeçar?
R.: Em um modelo mais simples, podemos salvar o estado de autorizado pelo gateway em nosso banco de dados e rodar um job que roda de um em um minuto que captura os pagamentos autorizados.
(Existem maneiras mais sofisticadas de resolver isso como disparando um novo evento para uma fila? Sim. Mas o job de um em um minuto já vai segurar bem)
@Component
public class PspAuthorizationWorker {
private final PaymentRepository repository;
private final PspGateway pspGateway;
@Transactional
public void handle(CreatePaymentEvent event) {
Payment payment = repository.findById(event.paymentId())
.orElseThrow();
if (payment.getStatus() != PaymentStatus.PSP_CREATION_PENDING) {
return;
}
String txId = pspGateway.authorize(payment);
payment.authorize(txId);
repository.save(payment);
}
}
@Component
@Transactional(rollbackFor = Exception.class)
class PaymentCaptureScheduler {
private final PaymentRepository repository;
private final PSPClient pspClient;
PaymentCaptureScheduler(PaymentRepository repository, PSPClient pspClient) {
this.repository = repository;
this.pspClient = pspClient;
}
@Scheduled(fixedDelay = 60000)
public void capturePendingPayments() {
List<Payment> payments = repository.findAllByStatus(PaymentStatus.CAPTURE_PENDING);
payments.forEach(this::capture);
}
void capture(Payment payment) {
try {
pspClient.capture(payment.getPspTransactionId());
} catch (Exception e) {
// log + métrica
// retry natural no próximo ciclo
}
}
}
Ainda existem uma série de melhorias que podem ser feitas nesse código para evitar a concorrência e manter a integridade dos registros, mas vou encerrar meus trabalhos por aqui. Gostaria de deixar duas perguntas de exemplo para que possa pensar o que fazer:
- O que aconteceria se duas máquinas rodassem o job ao mesmo tempo? Existe um jeito de impedir que isso aconteça?
- Como faço para receber o retorno do PSP sobre a resposta de captura do pagamento, uma vez que ele é assíncrono?
A medida que este sistema cresce, novas medidas se tornarão necessárias e deveremos aplicar novas técnicas de otimização, visando a integridade dos serviços prestados por nós, pois a medida que crescemos nossos desafios aumentam também.
Ao utilizar um agent de IA para codificar, não se esqueça de constantemente realizar essas perguntas, identificando pontos de falha e melhorias.
Conclusão
Ferramentas vem para nos dar suporte para sermos mais eficientes. Daqui há 5, 10, 20 anos, novas ferramentas surgirão e teremos que nos adaptar a elas, porque faz parte do jogo.
Para prosperar nesta nova era, a receita se mantém a mesma: fortaleça seus alicerces. Aprenda os fundamentos, entenda a fundo o problema que você está resolvendo e nunca se esqueça do passado para não cometer os mesmos erros no futuro.

Top comments (2)
Ótimo texto! A qualidade de software guiada por um conhecimento de regra de negócio é a base do que fazemos há décadas, e deverá permanecer assim, quando queremos transicionar entre níveis de senioridade acho que esse é um elemento mais que fundamental. Análise muito sólida, muito bom!
É esse o espírito, Rafael!