Nuuvify.CommonPack.MftMailbox.Abstraction 2.6.0-test.26070804

This is a prerelease version of Nuuvify.CommonPack.MftMailbox.Abstraction.
dotnet add package Nuuvify.CommonPack.MftMailbox.Abstraction --version 2.6.0-test.26070804
                    
NuGet\Install-Package Nuuvify.CommonPack.MftMailbox.Abstraction -Version 2.6.0-test.26070804
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nuuvify.CommonPack.MftMailbox.Abstraction" Version="2.6.0-test.26070804" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nuuvify.CommonPack.MftMailbox.Abstraction" Version="2.6.0-test.26070804" />
                    
Directory.Packages.props
<PackageReference Include="Nuuvify.CommonPack.MftMailbox.Abstraction" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nuuvify.CommonPack.MftMailbox.Abstraction --version 2.6.0-test.26070804
                    
#r "nuget: Nuuvify.CommonPack.MftMailbox.Abstraction, 2.6.0-test.26070804"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nuuvify.CommonPack.MftMailbox.Abstraction@2.6.0-test.26070804
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nuuvify.CommonPack.MftMailbox.Abstraction&version=2.6.0-test.26070804&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Nuuvify.CommonPack.MftMailbox.Abstraction&version=2.6.0-test.26070804&prerelease
                    
Install as a Cake Tool

Nuuvify.CommonPack.MftMailbox.Abstraction

PR Validation Publish and Release NuGet Downloads

Pacote de contratos e modelos compartilhados para integração com MFT Mailbox.

Este pacote define o contrato estável para envio, recebimento, consulta de status e confirmação ACK/NACK em fluxos de transferência de arquivos por SFTP e HTTPS.

Índice

Quando usar

  • Quando o projeto terceiro precisa depender apenas de abstrações MFT.
  • Quando você quer desacoplar domínio/aplicação da implementação concreta (SFTP/HTTPS).
  • Quando o consumo deve ser testável por mocks sem dependência de infraestrutura.

Pacotes relacionados

  • Nuuvify.CommonPack.MftMailbox: núcleo com factory, idempotência e auditoria padrão.
  • Nuuvify.CommonPack.MftMailbox.Sftp: implementação do adapter SFTP.
  • Nuuvify.CommonPack.MftMailbox.Http: implementação do adapter HTTPS Mailbox.

Instalação

<PackageReference Include="Nuuvify.CommonPack.MftMailbox.Abstraction" Version="x.x.x" />

Contratos principais

Interfaces de operação

  • IMftTransferClient
    • SendSingleAsync
    • SendBatchAsync
  • IMftInboundClient
    • ReceiveSingleAsync
    • ReceiveBatchAsync
  • IMftStatusClient
    • GetStatusAsync
  • IAckNackClient
    • AckOrNackAsync

Interface de fábrica

  • IMftClientFactory
    • CreateTransferClient
    • CreateInboundClient
    • CreateStatusClient
    • CreateAckNackClient

Extensões de infraestrutura

  • IMftIdempotencyStore
  • ITransferAuditSink

Modelos principais

  • TransferEnvelope: contexto da operação (integração, correlação, protocolo e itens).
  • TransferItem: unidade de transferência com metadados e ContentFactory para streaming.
  • TransferItemResult e TransferBatchResult: resultado por item e consolidado por lote.
  • TransferStatus: status observável da transferência.
  • AckNackCommand: comando de confirmação (ACK/NACK).
  • InboundTransferItem: stream de recebimento para consumo do cliente.
  • TransferAuditEntry: trilha de auditoria por item/operação.

ACK/NACK - Confirmação de processamento

O que é ACK/NACK?

ACK/NACK é um mecanismo de confirmação de processamento para transferências de arquivos recebidas. Funciona como um handshake entre cliente e servidor:

  • ACK (Acknowledgment): "Recebi e processei o arquivo com sucesso" → remove arquivo da caixa de entrada
  • NACK (Negative Acknowledgment): "Recebi, mas houve erro no processamento" → mantém arquivo na caixa para reprocessamento

Comportamento esperado

1. Arquivo chega na caixa de entrada (Mailbox)
   ↓
2. Sua aplicação recebe o arquivo via ReceiveSingleAsync
   ↓
3. Aplicação processa o arquivo (parseia, valida, persiste)
   ↓
4a. SUCESSO: AckOrNackAsync(..., isAck: true)
    └─ Arquivo removido da caixa
    └─ Não será reprocessado
   ↓
4b. ERRO: AckOrNackAsync(..., isAck: false)
    └─ Arquivo mantido na caixa
    └─ Reprocessamento automático na próxima tentativa
    └─ Histórico de tentativas preservado

Exemplo 1: Processamento bem-sucedido (ACK)

using Nuuvify.CommonPack.MftMailbox.Abstraction.Interfaces;
using Nuuvify.CommonPack.MftMailbox.Abstraction.Models;

public class InboundOrderProcessor
{
    private readonly IMftClientFactory _factory;
    private readonly IOrderRepository _orderRepository;
    private readonly ILogger<InboundOrderProcessor> _logger;

    public InboundOrderProcessor(
        IMftClientFactory factory,
        IOrderRepository orderRepository,
        ILogger<InboundOrderProcessor> logger)
    {
        _factory = factory;
        _orderRepository = orderRepository;
        _logger = logger;
    }

    public async Task ProcessOrderFileAsync(string integrationKey, CancellationToken ct)
    {
        var inboundClient = _factory.CreateInboundClient(MftProtocol.Sftp);
        var ackNackClient = _factory.CreateAckNackClient(MftProtocol.Sftp);

        // 1. Receber arquivo
        var envelope = new TransferEnvelope
        {
            IntegrationKey = integrationKey,
            Protocol = MftProtocol.Sftp
        };

        var result = await inboundClient.ReceiveSingleAsync(envelope, ct);

        if (result.Item is null)
        {
            _logger.LogInformation("Nenhum arquivo disponível na caixa de entrada.");
            return;
        }

        try
        {
            // 2. Processar arquivo
            using var stream = await result.Item.ContentFactory(ct);
            var orders = await ParseOrdersAsync(stream, ct);

            // 3. Persistir no banco
            await _orderRepository.InsertAsync(orders, ct);

            _logger.LogInformation(
                "Arquivo {FileName} processado: {Count} pedidos importados",
                result.Item.FileName,
                orders.Count);

            // ✅ 4. ACK: Arquivo processado com sucesso
            var ackCommand = new AckNackCommand
            {
                IntegrationKey = integrationKey,
                ItemId = result.Item.ItemId,
                FileName = result.Item.FileName,
                IsAck = true,
                Message = $"Importados {orders.Count} pedidos com sucesso"
            };

            await ackNackClient.AckOrNackAsync(ackCommand, ct);
            _logger.LogInformation("ACK enviado para {FileName}", result.Item.FileName);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Erro ao processar {FileName}", result.Item?.FileName);

            // ❌ Se houver erro: NACK para reprocessamento
            if (result.Item is not null)
            {
                var nackCommand = new AckNackCommand
                {
                    IntegrationKey = integrationKey,
                    ItemId = result.Item.ItemId,
                    FileName = result.Item.FileName,
                    IsAck = false,
                    Message = $"Erro: {ex.Message}"
                };

                await ackNackClient.AckOrNackAsync(nackCommand, ct);
                _logger.LogWarning("NACK enviado para {FileName} (será reprocessado)", result.Item.FileName);
            }

            throw;
        }
    }

    private async Task<List<Order>> ParseOrdersAsync(Stream stream, CancellationToken ct)
    {
        // Lógica de parsing do CSV/XML
        var orders = new List<Order>();
        // ... parsing logic ...
        return orders;
    }
}

Exemplo 2: Processamento com erro validação (NACK)

public class OrderValidationProcessor
{
    private readonly IMftClientFactory _factory;
    private readonly IOrderValidator _validator;
    private readonly ILogger<OrderValidationProcessor> _logger;

    public async Task ProcessWithValidationAsync(string integrationKey, CancellationToken ct)
    {
        var inboundClient = _factory.CreateInboundClient(MftProtocol.Sftp);
        var ackNackClient = _factory.CreateAckNackClient(MftProtocol.Sftp);

        var envelope = new TransferEnvelope
        {
            IntegrationKey = integrationKey,
            Protocol = MftProtocol.Sftp
        };

        var result = await inboundClient.ReceiveSingleAsync(envelope, ct);

        if (result.Item is null)
            return;

        try
        {
            using var stream = await result.Item.ContentFactory(ct);
            var orders = await ParseOrdersAsync(stream, ct);

            // Validar cada pedido
            var validationErrors = new List<string>();
            foreach (var order in orders)
            {
                var validationResult = await _validator.ValidateAsync(order, ct);
                if (!validationResult.IsValid)
                {
                    validationErrors.AddRange(validationResult.Errors.Select(e => e.Message));
                }
            }

            if (validationErrors.Any())
            {
                // ❌ NACK: Validação falhou, arquivo será reprocessado
                var nackCommand = new AckNackCommand
                {
                    IntegrationKey = integrationKey,
                    ItemId = result.Item.ItemId,
                    FileName = result.Item.FileName,
                    IsAck = false,
                    Message = $"Validação falhou: {string.Join("; ", validationErrors)}"
                };

                await ackNackClient.AckOrNackAsync(nackCommand, ct);

                _logger.LogWarning(
                    "NACK enviado para {FileName} - Erros de validação",
                    result.Item.FileName);

                return;
            }

            // ✅ ACK: Validação passou
            var ackCommand = new AckNackCommand
            {
                IntegrationKey = integrationKey,
                ItemId = result.Item.ItemId,
                FileName = result.Item.FileName,
                IsAck = true,
                Message = $"Validado com sucesso: {orders.Count} pedidos"
            };

            await ackNackClient.AckOrNackAsync(ackCommand, ct);
            _logger.LogInformation("ACK enviado para {FileName}", result.Item.FileName);
        }
        catch (Exception ex)
        {
            // ❌ NACK em caso de exceção
            var nackCommand = new AckNackCommand
            {
                IntegrationKey = integrationKey,
                ItemId = result.Item.ItemId,
                FileName = result.Item.FileName,
                IsAck = false,
                Message = ex.Message
            };

            await ackNackClient.AckOrNackAsync(nackCommand, ct);
            _logger.LogError(ex, "Erro crítico - NACK enviado para {FileName}", result.Item.FileName);

            throw;
        }
    }

    private async Task<List<Order>> ParseOrdersAsync(Stream stream, CancellationToken ct)
    {
        var orders = new List<Order>();
        // ... parsing logic ...
        return orders;
    }
}

Exemplo 3: Fluxo com retry automático (Worker pattern)

public class MftInboundWorker : BackgroundService
{
    private readonly IMftClientFactory _factory;
    private readonly IOrderProcessor _processor;
    private readonly ILogger<MftInboundWorker> _logger;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        var inboundClient = _factory.CreateInboundClient(MftProtocol.Sftp);
        var ackNackClient = _factory.CreateAckNackClient(MftProtocol.Sftp);

        while (!ct.IsCancellationRequested)
        {
            try
            {
                // Polling da caixa de entrada
                var envelope = new TransferEnvelope
                {
                    IntegrationKey = "sap-orders",
                    Protocol = MftProtocol.Sftp
                };

                var result = await inboundClient.ReceiveSingleAsync(envelope, ct);

                if (result.Item is null)
                {
                    _logger.LogDebug("Nenhum arquivo disponível. Próxima tentativa em 30s");
                    await Task.Delay(TimeSpan.FromSeconds(30), ct);
                    continue;
                }

                _logger.LogInformation(
                    "Arquivo recebido: {FileName} ({ItemId})",
                    result.Item.FileName,
                    result.Item.ItemId);

                // Tentar processar com retry
                var success = await ProcessWithRetryAsync(
                    result.Item,
                    envelope.IntegrationKey,
                    ackNackClient,
                    ct);

                if (success)
                {
                    _logger.LogInformation(
                        "Arquivo {FileName} processado com sucesso",
                        result.Item.FileName);
                }
                else
                {
                    _logger.LogWarning(
                        "Arquivo {FileName} não processado - será reprocessado na próxima tentativa",
                        result.Item.FileName);
                }
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation("Worker cancelado");
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Erro inesperado no worker");
                await Task.Delay(TimeSpan.FromSeconds(60), ct);
            }
        }
    }

    private async Task<bool> ProcessWithRetryAsync(
        InboundTransferItem item,
        string integrationKey,
        IAckNackClient ackNackClient,
        CancellationToken ct)
    {
        const int maxRetries = 3;
        var retryCount = 0;

        while (retryCount < maxRetries)
        {
            try
            {
                // Processar arquivo
                using var stream = await item.ContentFactory(ct);
                var result = await _processor.ProcessAsync(stream, ct);

                // ✅ Sucesso: ACK
                var ackCommand = new AckNackCommand
                {
                    IntegrationKey = integrationKey,
                    ItemId = item.ItemId,
                    FileName = item.FileName,
                    IsAck = true,
                    Message = $"Processado na tentativa {retryCount + 1}"
                };

                await ackNackClient.AckOrNackAsync(ackCommand, ct);
                return true;
            }
            catch (Exception ex)
            {
                retryCount++;

                _logger.LogWarning(
                    ex,
                    "Erro ao processar {FileName} (tentativa {Retry}/{Max})",
                    item.FileName,
                    retryCount,
                    maxRetries);

                if (retryCount >= maxRetries)
                {
                    // ❌ Falha após retries: NACK
                    var nackCommand = new AckNackCommand
                    {
                        IntegrationKey = integrationKey,
                        ItemId = item.ItemId,
                        FileName = item.FileName,
                        IsAck = false,
                        Message = $"Falha após {maxRetries} tentativas: {ex.Message}"
                    };

                    await ackNackClient.AckOrNackAsync(nackCommand, ct);
                    return false;
                }

                // Aguardar antes de retry
                await Task.Delay(TimeSpan.FromSeconds(5 * retryCount), ct);
            }
        }

        return false;
    }
}

Tabela de decisão: ACK vs NACK

Situação Ação Comportamento esperado
Arquivo parseado e persistido com sucesso ACK Arquivo removido da caixa
Validação falhou (dados inválidos) NACK Arquivo mantido para reprocessamento
Banco de dados indisponível NACK Retry automático na próxima execução
Encoding/formato inválido NACK Arquivo fica em "quarentena" aguardando correção
Processamento bem-sucedido mas com warnings ACK Arquivo removido (warnings são logados)

Boas práticas com ACK/NACK

  • Sempre enviar ACK/NACK: Não deixe arquivo "órfão" na caixa
  • ACK apenas após persistência: Confirme sucesso após salvar no banco
  • NACK para erros recuperáveis: Validação, timeout, recursos temporariamente indisponíveis
  • Logging detalhado: Use Message no AckNackCommand para rastrear motivo
  • Timeout razoável: Configure timeout de recebimento compatível com tamanho de arquivo
  • Não fazer ACK antes de processar: Se a aplicação falhar, arquivo é perdido
  • Ignorar NACK: Sempre aguarde confirmação do servidor

Exemplo de uso no domínio/aplicação

using Nuuvify.CommonPack.MftMailbox.Abstraction.Interfaces;
using Nuuvify.CommonPack.MftMailbox.Abstraction.Models;

public sealed class FileOutboundUseCase
{
		private readonly IMftClientFactory _factory;

		public FileOutboundUseCase(IMftClientFactory factory)
		{
				_factory = factory;
		}

		public async Task<TransferItemResult> ExecuteAsync(Stream stream, CancellationToken ct)
		{
				var transferClient = _factory.CreateTransferClient(MftProtocol.Sftp);

				var envelope = new TransferEnvelope
				{
						IntegrationKey = "orders",
						CorrelationId = Guid.NewGuid().ToString("N"),
						Protocol = MftProtocol.Sftp,
						Items = new List<TransferItem>
						{
								new()
								{
										ItemId = Guid.NewGuid().ToString("N"),
										FileName = "orders.csv",
										ContentFactory = _ => Task.FromResult(stream)
								}
						}
				};

				return await transferClient.SendSingleAsync(envelope, ct);
		}
}

Boas práticas

  • Mantenha domínio/aplicação dependente apenas deste pacote.
  • Resolva implementação concreta no entrypoint via DI.
  • Propague CancellationToken em toda cadeia de operação.
  • Trate status e resultado por item para rastreabilidade.

Compatibilidade

  • Framework alvo: .NET 8
  • Sem dependência de protocolo específico nesta camada.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.6.0-test.26070804 1 7/8/2026

# Changelog - Nuuvify.CommonPack.MftMailbox.Abstraction

Todas as mudancas notaveis deste pacote serao documentadas neste arquivo.

O formato e baseado em [Keep a Changelog](https://keepachangelog.com/pt-br/1.0.0/),
e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/spec/v2.0.0.html).

## [Nao Lancado]

### Adicionado
- Contratos base para clientes MFT Mailbox com suporte a SFTP e HTTPS.

### Alterado

### Corrigido

### Removido

### Seguranca