Avaliação: Backend for Frontend (BFF) - Arah

Data: 2026-01-27
Status: 📋 Avaliação Estratégica
Objetivo: Avaliar criação de BFF que reflita as principais jornadas da API atual, expondo operações em forma de jornadas e criando camada de abstração entre interfaces visuais e backend


Criar um Backend for Frontend (BFF) que:

  1. Reflita as principais jornadas da API atual
  2. Exponha gentilmente as operações em forma de jornadas (user journeys)
  3. Crie camada de abstração entre aplicações de interfaces visuais e o backend
  4. Encapsule responsabilidades de UX/UI em conformidade com operações já expostas na API
  5. Use arquitetura modular existente

📊 Análise da Situação Atual

A API atual (Arah.Api) expõe endpoints RESTful organizados por recursos:

/api/v1/auth/*          - Autenticação
/api/v1/territories/*   - Territórios
/api/v1/feed/*          - Feed comunitário
/api/v1/events/*        - Eventos
/api/v1/map/*           - Mapa territorial
/api/v1/marketplace/*   - Marketplace
/api/v1/chat/*          - Chat
/api/v1/alerts/*        - Alertas
/api/v1/notifications/*  - Notificações
/api/v1/moderation/*    - Moderação
...

Características:

  • ✅ Endpoints RESTful bem estruturados
  • ✅ Organização por recursos/domínios
  • ✅ Arquitetura modular (módulos por funcionalidade)
  • ⚠️ Frontend precisa fazer múltiplas chamadas para completar uma jornada
  • ⚠️ Lógica de agregação/composição fica no frontend
  • ⚠️ Transformações de dados para UI ficam no frontend

Problemas Identificados

Exemplo: Criar Post com Mídia

Frontend precisa:
1. POST /api/v1/media/upload (upload de mídia)
2. POST /api/v1/feed (criar post com mediaIds)
3. GET /api/v1/feed/{id} (buscar post criado para exibir)

Com BFF:

1. POST /api/v2/journeys/feed/create-post (faz tudo internamente)

Exemplo: Feed do Território

Frontend precisa:
1. GET /api/v1/feed (posts)
2. GET /api/v1/feed/{id}/counts (contadores para cada post)
3. GET /api/v1/media/{id} (mídias para cada post)
4. GET /api/v1/events/{id} (eventos relacionados)
5. Agregar tudo no frontend

Com BFF:

1. GET /api/v2/journeys/feed/territory-feed (retorna tudo agregado)

Exemplo: Evento com Participantes

API retorna:
- Event (objeto de domínio)
- Participants (lista separada)
- Media (lista separada)

Frontend precisa transformar para:
- EventCard (componente UI)
- EventDetails (componente UI)

Com BFF:

BFF retorna:
- EventCardResponse (já formatado para UI)
- EventDetailsResponse (já formatado para UI)

🏗️ Proposta de Arquitetura BFF Modular

┌─────────────────────────────────────────────────────────┐
│              Aplicações de Interface Visual              │
│  (Flutter App, Web App, Admin Dashboard, etc.)          │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ HTTP/REST
                     │
┌────────────────────▼────────────────────────────────────┐
│              Arah.Api.Bff (BFF Layer)                 │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Journey Controllers (por jornada)                 │  │
│  │  - FeedJourneyController                           │  │
│  │  - EventJourneyController                         │  │
│  │  - MarketplaceJourneyController                    │  │
│  │  - TerritoryJourneyController                     │  │
│  │  - OnboardingJourneyController                    │  │
│  └────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Journey Services (orquestração)                   │  │
│  │  - FeedJourneyService                             │  │
│  │  - EventJourneyService                           │  │
│  │  - MarketplaceJourneyService                      │  │
│  └────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Response Transformers (UI formatting)            │  │
│  │  - FeedResponseTransformer                        │  │
│  │  - EventResponseTransformer                      │  │
│  └────────────────────────────────────────────────────┘  │
└────────────────────┬────────────────────────────────────┘
                     │
                     │ HTTP/REST (interno)
                     │
┌────────────────────▼────────────────────────────────────┐
│         Arah.Api (API Principal - Existente)        │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Controllers (por recurso)                         │  │
│  │  - FeedController                                  │  │
│  │  - EventsController                                │  │
│  │  - MarketplaceController                           │  │
│  └────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Application Services (lógica de negócio)          │  │
│  │  - FeedService                                     │  │
│  │  - EventsService                                  │  │
│  └────────────────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────────────────┐  │
│  │  Modules (arquitetura modular)                    │  │
│  │  - FeedModule                                      │  │
│  │  - EventsModule                                   │  │
│  │  - MarketplaceModule                               │  │
│  └────────────────────────────────────────────────────┘  │
└───────────────────────────────────────────────────────────┘
Arah.Api.Bff/
├── Controllers/
│   ├── Journeys/
│   │   ├── FeedJourneyController.cs
│   │   ├── EventJourneyController.cs
│   │   ├── MarketplaceJourneyController.cs
│   │   ├── TerritoryJourneyController.cs
│   │   ├── OnboardingJourneyController.cs
│   │   ├── ChatJourneyController.cs
│   │   └── ProfileJourneyController.cs
│   └── HealthController.cs
├── Services/
│   ├── Journeys/
│   │   ├── IFeedJourneyService.cs
│   │   ├── FeedJourneyService.cs
│   │   ├── IEventJourneyService.cs
│   │   ├── EventJourneyService.cs
│   │   └── ...
│   └── Transformers/
│       ├── IFeedResponseTransformer.cs
│       ├── FeedResponseTransformer.cs
│       └── ...
├── Contracts/
│   ├── Journeys/
│   │   ├── FeedJourney/
│   │   │   ├── CreatePostJourneyRequest.cs
│   │   │   ├── CreatePostJourneyResponse.cs
│   │   │   ├── TerritoryFeedJourneyResponse.cs
│   │   │   └── ...
│   │   └── ...
│   └── Common/
│       └── JourneyResponse.cs
├── Program.cs
└── Arah.Api.Bff.csproj

Integração com Arquitetura Modular

⚠️ ATUALIZAÇÃO ARQUITETURAL: Após reavaliação, o BFF será implementado como aplicação externa (não como módulo interno).

Estratégia de Evolução:

O BFF pode começar como módulo interno para simplicidade:

namespace Arah.Modules.Bff;

public class BffModule : ModuleBase
{
    public override string Id => "BFF";
    
    public override string[] DependsOn => new[]
    {
        "Core",
        "Feed",
        "Events",
        "Marketplace",
        "Map",
        "Chat"
    };
    
    public override bool IsRequired => false; // Opcional, pode desabilitar
    
    public override void RegisterServices(
        IServiceCollection services, 
        IConfiguration configuration)
    {
        // Registrar serviços de jornadas
        services.AddScoped<IFeedJourneyService, FeedJourneyService>();
        services.AddScoped<IEventJourneyService, EventJourneyService>();
        // ...
        
        // Registrar transformers
        services.AddScoped<IFeedResponseTransformer, FeedResponseTransformer>();
        // ...
    }
}

Vantagens (Fase 1):

  • ✅ Simplicidade e zero custo adicional
  • ✅ Comunicação in-process (sem latência de rede)
  • ✅ Um único deploy
  • ✅ Coexiste com API v1

Quando migrar para APIs Modulares ou precisar de escalabilidade independente:

Arquitetura:

Arah.Api.Bff/ (aplicação separada)
├── OAuth2 Authorization Server
├── Journey Controllers
├── Journey Services
└── API Client (consome API principal via HTTP)

Integração com Módulos:

  • ✅ BFF consome APIs modulares via HTTP
  • ✅ Autenticação própria (OAuth2 Client Credentials)
  • ✅ Registro de múltiplos apps consumidores
  • ✅ Escalabilidade independente
  • ✅ Separação de responsabilidades

Vantagens (Fase 2):

  • ✅ Escalabilidade independente
  • ✅ Separação de responsabilidades
  • ✅ Preparado para microserviços
  • ✅ Evolução independente

Ver documentação completa: REAVALIACAO_BFF_MODULO_VS_APLICACAO_EXTERNA.md


🗺️ Mapeamento de Jornadas Principais

Fluxo Atual (múltiplas chamadas):

1. POST /api/v1/auth/social (login)
2. GET /api/v1/territories (listar territórios)
3. GET /api/v1/territories/nearby (territórios próximos)
4. POST /api/v1/territories/selection (selecionar território)
5. POST /api/v1/territories/{id}/enter (entrar como VISITOR)
6. GET /api/v1/feed?territoryId={id} (feed inicial)

Com BFF (jornada única):

POST /api/v2/journeys/onboarding/complete
Request: {
  "authToken": "...",
  "selectedTerritoryId": "...",
  "location": { "lat": ..., "lng": ... }
}
Response: {
  "user": { ... },
  "territory": { ... },
  "initialFeed": [ ... ],
  "suggestedActions": [ ... ]
}

Benefícios:

  • ✅ Reduz de 6 para 1 chamada
  • ✅ Retorna contexto completo para UI
  • ✅ Sugere próximas ações (UX melhorado)

Fluxo Atual:

1. POST /api/v1/media/upload (upload de cada mídia)
2. POST /api/v1/feed (criar post com mediaIds)
3. GET /api/v1/feed/{id} (buscar post criado)

Com BFF:

POST /api/v2/journeys/feed/create-post
Request: {
  "title": "...",
  "content": "...",
  "type": "POST",
  "visibility": "PUBLIC",
  "mediaFiles": [ ... ], // Multipart form data
  "tags": [ ... ],
  "location": { ... }
}
Response: {
  "post": { ... }, // Post completo formatado para UI
  "mediaUrls": [ ... ],
  "suggestions": {
    "similarPosts": [ ... ],
    "suggestedTags": [ ... ]
  }
}

Benefícios:

  • ✅ Upload e criação em uma única chamada
  • ✅ Retorna post formatado para UI
  • ✅ Sugestões contextuais (UX melhorado)

Fluxo Atual:

1. GET /api/v1/feed?territoryId={id} (posts)
2. Para cada post:
   - GET /api/v1/feed/{id}/counts (contadores)
   - GET /api/v1/media?ownerType=Post&ownerId={id} (mídias)
   - Se for evento: GET /api/v1/events/{id} (detalhes do evento)

Com BFF:

GET /api/v2/journeys/feed/territory-feed?territoryId={id}&page=1&pageSize=20
Response: {
  "items": [
    {
      "post": { ... },
      "counts": { "likes": 10, "shares": 5, "comments": 3 },
      "media": [ ... ],
      "event": { ... }, // Se for post de evento
      "author": { ... },
      "interactions": {
        "userLiked": false,
        "userShared": false,
        "userCommented": false
      }
    }
  ],
  "pagination": { ... },
  "filters": {
    "availableTypes": [ ... ],
    "availableTags": [ ... ]
  }
}

Benefícios:

  • ✅ Reduz de N+1 para 1 chamada
  • ✅ Retorna dados agregados e formatados
  • ✅ Inclui estado de interações do usuário
  • ✅ Fornece metadados para filtros (UX melhorado)

Fluxo Atual:

1. GET /api/v1/events/{id} (detalhes do evento)
2. GET /api/v1/events/{id}/participants (participantes)
3. POST /api/v1/events/{id}/interest (marcar interesse)
4. GET /api/v1/events/{id} (atualizar detalhes)

Com BFF:

POST /api/v2/journeys/events/participate
Request: {
  "eventId": "...",
  "action": "INTEREST" | "CONFIRM" | "CANCEL"
}
Response: {
  "event": { ... }, // Evento atualizado
  "userParticipation": {
    "status": "INTERESTED",
    "confirmed": false
  },
  "participants": {
    "interested": [ ... ],
    "confirmed": [ ... ]
  },
  "suggestions": {
    "similarEvents": [ ... ],
    "nearbyEvents": [ ... ]
  }
}

Benefícios:

  • ✅ Ação e atualização em uma chamada
  • ✅ Retorna contexto completo atualizado
  • ✅ Sugestões contextuais (UX melhorado)

Fluxo Atual:

1. GET /api/v1/stores?territoryId={id} (lojas)
2. GET /api/v1/items?storeId={id} (itens)
3. GET /api/v1/items/{id} (detalhes do item)
4. POST /api/v1/cart (adicionar ao carrinho)
5. GET /api/v1/cart (ver carrinho)
6. POST /api/v1/cart/checkout (finalizar compra)

Com BFF:

# Buscar itens
GET /api/v2/journeys/marketplace/search?territoryId={id}&query=...
Response: {
  "items": [ ... ], // Itens formatados para UI
  "stores": [ ... ],
  "filters": { ... },
  "suggestions": [ ... ]
}

# Adicionar ao carrinho e verificar
POST /api/v2/journeys/marketplace/add-to-cart
Request: { "itemId": "...", "quantity": 1 }
Response: {
  "cart": { ... },
  "item": { ... },
  "total": { ... },
  "suggestions": {
    "frequentlyBoughtTogether": [ ... ],
    "similarItems": [ ... ]
  }
}

# Finalizar compra
POST /api/v2/journeys/marketplace/checkout
Request: { "paymentMethod": "...", "shippingAddress": { ... } }
Response: {
  "order": { ... },
  "payment": { ... },
  "confirmation": { ... }
}

Benefícios:

  • ✅ Reduz múltiplas chamadas para jornadas únicas
  • ✅ Retorna dados agregados e formatados
  • ✅ Sugestões de produtos (UX melhorado)

🎨 Transformações de Dados para UI

public class FeedResponseTransformer : IFeedResponseTransformer
{
    public TerritoryFeedJourneyResponse Transform(
        IEnumerable<Post> posts,
        Dictionary<Guid, PostCounts> counts,
        Dictionary<Guid, IReadOnlyList<string>> mediaUrls,
        Dictionary<Guid, EventSummary> events,
        Guid? currentUserId)
    {
        return new TerritoryFeedJourneyResponse
        {
            Items = posts.Select(post => new FeedItemJourneyResponse
            {
                // Dados do post formatados para UI
                Id = post.Id,
                Title = post.Title,
                Content = post.Content,
                Type = post.Type.ToString().ToUpperInvariant(),
                Visibility = post.Visibility.ToString().ToUpperInvariant(),
                
                // Contadores agregados
                Counts = new PostCountsResponse
                {
                    Likes = counts.GetValueOrDefault(post.Id, new PostCounts(0, 0)).LikeCount,
                    Shares = counts.GetValueOrDefault(post.Id, new PostCounts(0, 0)).ShareCount,
                    Comments = counts.GetValueOrDefault(post.Id, new PostCounts(0, 0)).CommentCount
                },
                
                // Mídias formatadas
                Media = mediaUrls.GetValueOrDefault(post.Id, Array.Empty<string>())
                    .Select(url => new MediaResponse { Url = url, Type = "IMAGE" })
                    .ToList(),
                
                // Evento relacionado (se houver)
                Event = post.ReferenceType == "EVENT" && events.ContainsKey(post.ReferenceId.Value)
                    ? TransformEvent(events[post.ReferenceId.Value])
                    : null,
                
                // Estado de interações do usuário
                UserInteractions = new UserInteractionsResponse
                {
                    Liked = false, // Será preenchido pelo serviço
                    Shared = false,
                    Commented = false
                },
                
                // Metadados para UI
                Metadata = new PostMetadataResponse
                {
                    CanEdit = post.AuthorUserId == currentUserId,
                    CanDelete = post.AuthorUserId == currentUserId,
                    CanShare = true,
                    CanComment = true
                }
            }).ToList(),
            
            // Metadados para filtros e paginação
            Filters = new FeedFiltersResponse
            {
                AvailableTypes = Enum.GetValues<PostType>().Select(t => t.ToString()).ToList(),
                AvailableTags = ExtractTags(posts),
                AvailableVisibilities = Enum.GetValues<PostVisibility>().Select(v => v.ToString()).ToList()
            }
        };
    }
}

🔧 Implementação Técnica

backend/
├── Arah.Api.Bff/              # Novo projeto BFF
│   ├── Controllers/
│   │   └── Journeys/
│   ├── Services/
│   │   ├── Journeys/
│   │   └── Transformers/
│   ├── Contracts/
│   │   └── Journeys/
│   └── Arah.Api.Bff.csproj
│
└── Arah.Modules.Bff/          # Módulo BFF
    └── BffModule.cs
public class FeedJourneyService : IFeedJourneyService
{
    private readonly IFeedService _feedService;
    private readonly MediaService _mediaService;
    private readonly EventsService _eventsService;
    private readonly IFeedResponseTransformer _transformer;
    private readonly CurrentUserAccessor _currentUserAccessor;
    
    public async Task<TerritoryFeedJourneyResponse> GetTerritoryFeedAsync(
        Guid territoryId,
        Guid? userId,
        int pageNumber,
        int pageSize,
        CancellationToken cancellationToken)
    {
        // 1. Buscar posts (paginado)
        var pagination = new PaginationParameters(pageNumber, pageSize);
        var pagedResult = await _feedService.ListForTerritoryPagedAsync(
            territoryId,
            userId,
            null,
            null,
            pagination,
            false,
            cancellationToken);
        
        // 2. Buscar contadores (batch)
        var postIds = pagedResult.Items.Select(p => p.Id).ToList();
        var counts = await _feedService.GetCountsByPostIdsAsync(postIds, cancellationToken);
        
        // 3. Buscar mídias (batch)
        var mediaUrls = await LoadMediaUrlsBatchAsync(postIds, cancellationToken);
        
        // 4. Buscar eventos relacionados (batch)
        var eventLookup = await LoadEventSummariesAsync(pagedResult.Items, cancellationToken);
        
        // 5. Buscar interações do usuário (batch)
        var userInteractions = userId.HasValue
            ? await LoadUserInteractionsAsync(userId.Value, postIds, cancellationToken)
            : new Dictionary<Guid, UserInteractions>();
        
        // 6. Transformar para formato de jornada
        return _transformer.Transform(
            pagedResult.Items,
            counts,
            mediaUrls,
            eventLookup,
            userInteractions,
            pagedResult);
    }
    
    public async Task<CreatePostJourneyResponse> CreatePostAsync(
        Guid territoryId,
        Guid userId,
        CreatePostJourneyRequest request,
        CancellationToken cancellationToken)
    {
        // 1. Upload de mídias (se houver)
        var mediaIds = new List<Guid>();
        if (request.MediaFiles?.Any() == true)
        {
            foreach (var mediaFile in request.MediaFiles)
            {
                var uploadResult = await _mediaService.UploadMediaAsync(
                    territoryId,
                    userId,
                    mediaFile,
                    MediaOwnerType.Post,
                    null,
                    cancellationToken);
                
                if (uploadResult.IsSuccess && uploadResult.Value is not null)
                {
                    mediaIds.Add(uploadResult.Value.Id);
                }
            }
        }
        
        // 2. Criar post
        var postResult = await _feedService.CreatePostAsync(
            territoryId,
            userId,
            request.Title,
            request.Content,
            request.Type,
            request.Visibility,
            PostStatus.Published,
            request.MapEntityId,
            request.GeoAnchors,
            request.AssetIds,
            mediaIds,
            cancellationToken,
            request.Tags);
        
        if (!postResult.IsSuccess || postResult.Value is null)
        {
            return new CreatePostJourneyResponse
            {
                Success = false,
                Error = postResult.Error
            };
        }
        
        // 3. Buscar post completo formatado
        var post = postResult.Value;
        var mediaUrls = await LoadMediaUrlsForPostAsync(post.Id, cancellationToken);
        
        // 4. Buscar sugestões
        var suggestions = await GetPostSuggestionsAsync(territoryId, post, cancellationToken);
        
        return new CreatePostJourneyResponse
        {
            Success = true,
            Post = TransformPostForUI(post, mediaUrls),
            Suggestions = suggestions
        };
    }
}
[ApiController]
[Route("api/v2/journeys/feed")]
[Produces("application/json")]
[Tags("Feed Journeys")]
public class FeedJourneyController : ControllerBase
{
    private readonly IFeedJourneyService _journeyService;
    private readonly CurrentUserAccessor _currentUserAccessor;
    
    /// <summary>
    /// Obtém feed do território formatado para UI.
    /// </summary>
    [HttpGet("territory-feed")]
    [ProducesResponseType(typeof(TerritoryFeedJourneyResponse), StatusCodes.Status200OK)]
    public async Task<ActionResult<TerritoryFeedJourneyResponse>> GetTerritoryFeed(
        [FromQuery] Guid territoryId,
        [FromQuery] int pageNumber = 1,
        [FromQuery] int pageSize = 20,
        CancellationToken cancellationToken = default)
    {
        var userContext = await _currentUserAccessor.GetAsync(Request, cancellationToken);
        var userId = userContext.Status == TokenStatus.Valid ? userContext.User?.Id : null;
        
        var result = await _journeyService.GetTerritoryFeedAsync(
            territoryId,
            userId,
            pageNumber,
            pageSize,
            cancellationToken);
        
        return Ok(result);
    }
    
    /// <summary>
    /// Cria post com mídias em uma única chamada.
    /// </summary>
    [HttpPost("create-post")]
    [Consumes("multipart/form-data")]
    [ProducesResponseType(typeof(CreatePostJourneyResponse), StatusCodes.Status201Created)]
    public async Task<ActionResult<CreatePostJourneyResponse>> CreatePost(
        [FromQuery] Guid territoryId,
        [FromForm] CreatePostJourneyRequest request,
        CancellationToken cancellationToken = default)
    {
        var userContext = await _currentUserAccessor.GetAsync(Request, cancellationToken);
        if (userContext.Status != TokenStatus.Valid || userContext.User is null)
        {
            return Unauthorized();
        }
        
        var result = await _journeyService.CreatePostAsync(
            territoryId,
            userContext.User.Id,
            request,
            cancellationToken);
        
        if (!result.Success)
        {
            return BadRequest(new { error = result.Error });
        }
        
        return CreatedAtAction(
            nameof(GetTerritoryFeed),
            new { territoryId },
            result);
    }
}

📋 Jornadas Mapeadas

  1. Onboarding

    • POST /api/v2/journeys/onboarding/complete
    • GET /api/v2/journeys/onboarding/suggested-territories
  2. Feed

    • GET /api/v2/journeys/feed/territory-feed
    • POST /api/v2/journeys/feed/create-post
    • POST /api/v2/journeys/feed/interact (like/comment/share)
  3. Eventos

    • GET /api/v2/journeys/events/territory-events
    • POST /api/v2/journeys/events/create-event
    • POST /api/v2/journeys/events/participate
  4. Marketplace

    • GET /api/v2/journeys/marketplace/search
    • POST /api/v2/journeys/marketplace/add-to-cart
    • POST /api/v2/journeys/marketplace/checkout
  1. Territórios

    • GET /api/v2/journeys/territories/discover
    • POST /api/v2/journeys/territories/request-residency
  2. Chat

    • GET /api/v2/journeys/chat/conversations
    • POST /api/v2/journeys/chat/send-message
  3. Perfil

    • GET /api/v2/journeys/profile/me
    • PUT /api/v2/journeys/profile/update

✅ Vantagens do BFF

1. Redução de Chamadas de Rede

  • ✅ De 5-10 chamadas para 1 chamada por jornada
  • ✅ Menor latência percebida
  • ✅ Menor consumo de bateria (mobile)

2. Melhor Experiência do Usuário

  • ✅ Dados já formatados para UI
  • ✅ Sugestões contextuais
  • ✅ Estado completo em uma resposta

3. Encapsulamento de Lógica de UI

  • ✅ Transformações de dados no backend
  • ✅ Agregações complexas no backend
  • ✅ Frontend mais simples e focado em apresentação

4. Flexibilidade

  • ✅ Pode evoluir independentemente da API principal
  • ✅ Pode ter versões diferentes para diferentes clientes
  • ✅ Pode ser desabilitado se necessário

5. Compatibilidade

  • ✅ API principal continua funcionando
  • ✅ BFF é opcional (pode coexistir)
  • ✅ Migração gradual possível

⚠️ Desvantagens e Considerações

1. Complexidade Adicional

  • ⚠️ Mais uma camada para manter
  • ⚠️ Mais código para testar
  • ⚠️ Mais pontos de falha

2. Duplicação de Lógica

  • ⚠️ Alguma lógica pode ser duplicada entre BFF e API
  • ⚠️ Precisa manter sincronizado com mudanças na API

3. Overhead de Performance

  • ⚠️ Mais uma camada de processamento
  • ⚠️ Pode adicionar latência se mal implementado

4. Manutenção

  • ⚠️ Precisa manter contratos atualizados
  • ⚠️ Precisa manter transformadores atualizados

🎯 Recomendações

Objetivo: Implementar BFF básico com jornadas prioritárias

Tarefas:

  1. Criar projeto Arah.Api.Bff
  2. Criar módulo Arah.Modules.Bff
  3. Implementar jornadas prioritárias:
    • Onboarding
    • Feed (criar e visualizar)
    • Eventos (criar e participar)
  4. Implementar transformers básicos
  5. Testes unitários e de integração
  6. Documentação

Critérios de Sucesso:

  • ✅ BFF funcional com jornadas prioritárias
  • ✅ Redução de 70%+ nas chamadas de rede
  • ✅ Tempo de resposta < 500ms para jornadas principais

Fase 2: Expansão (4 semanas)

Objetivo: Adicionar jornadas secundárias e otimizações

Tarefas:

  1. Implementar jornadas secundárias
  2. Otimizações de performance (cache, batch)
  3. Melhorias de UX (sugestões, recomendações)
  4. Monitoramento e métricas

Fase 3: Otimização (2 semanas)

Objetivo: Otimizar performance e adicionar recursos avançados

Tarefas:

  1. Cache inteligente
  2. Prefetching de dados relacionados
  3. Streaming de respostas grandes
  4. GraphQL (opcional, se necessário)

📊 Métricas de Sucesso

Métricas Técnicas

  • Redução de chamadas: 70%+ de redução
  • Latência: < 500ms para jornadas principais
  • Throughput: Suportar 1000+ req/s
  • Disponibilidade: 99.9%+

Métricas de UX

  • Tempo de carregamento: Redução de 50%+
  • Interatividade: Melhoria de 30%+
  • Taxa de erro: < 1%

🔄 Estratégia de Migração

Opção 1: Coexistência (Recomendada)

  • ✅ BFF e API principal coexistem
  • ✅ Frontend migra gradualmente
  • ✅ API principal continua funcionando
  • ✅ Rollback fácil se necessário

Opção 2: Substituição Gradual

  • ⚠️ BFF substitui API principal gradualmente
  • ⚠️ Mais complexo, mas elimina duplicação
  • ⚠️ Requer planejamento cuidadoso

Opção 3: BFF Opcional

  • ✅ BFF é feature flag
  • ✅ Pode ser desabilitado
  • ✅ Teste A/B possível

  1. Contratos de API BFF

    • OpenAPI/Swagger para jornadas
    • Exemplos de requests/responses
    • Diagramas de sequência
  2. Guia de Desenvolvimento

    • Como criar nova jornada
    • Como criar transformer
    • Padrões e convenções
  3. Guia de Migração

    • Como migrar do endpoint antigo para jornada
    • Checklist de migração
    • Exemplos práticos

A criação de um Backend for Frontend (BFF) é altamente recomendada para o Arah porque:

  1. Reduz complexidade no frontend (menos chamadas, menos lógica)
  2. Melhora experiência do usuário (dados formatados, sugestões)
  3. Encapsula responsabilidades de UX/UI no backend
  4. Respeita arquitetura modular existente
  5. Permite evolução independente do frontend e backend
  6. Mantém compatibilidade com API existente

⚠️ IMPORTANTE - Reavaliação Arquitetural:

Esta proposta inicial sugeria o BFF como módulo interno. No entanto, foi realizada uma reavaliação arquitetural considerando a evolução planejada (Monolito → APIs Modulares → Microserviços).

Ver documentação atualizada: REAVALIACAO_BFF_MODULO_VS_APLICACAO_EXTERNA.md

Recomendação Atualizada: Estratégia Híbrida - Evolução Gradual

  • Fase 1 (Atual): BFF como módulo interno (simplicidade, zero custo)
  • Fase 2 (APIs Modulares): Migrar BFF para aplicação externa (escalabilidade independente)
  • Fase 3 (Microserviços): BFF já como aplicação externa (consome múltiplos serviços)

Próximos Passos:

  1. Aprovar proposta
  2. Implementar BFF como módulo interno (Fase 1)
  3. Implementar jornadas prioritárias
  4. Testar com frontend
  5. Planejar migração para aplicação externa (quando migrar para APIs Modulares)

Última Atualização: 2026-01-28
Status: 📋 Proposta Completa - Reavaliada e Atualizada
Ver Reavaliação: REAVALIACAO_BFF_MODULO_VS_APLICACAO_EXTERNA.md