Skip to content

INTEGRATIONS ARCHITECTURE

Arquitetura de Integrações do Sport Tech Club
Documentação técnica completa de todas as integrações externas e internas do sistema.

Versão: 1.0.0
Última Atualização: 2026-01-09
Responsável: Arquitetura de Software


Índice

  1. Visão Geral
  2. Princípios Arquiteturais
  3. Integrações Implementadas
  4. Integrações Planejadas
  5. Padrões de Integração
  6. Segurança
  7. Resiliência e Confiabilidade
  8. Monitoramento

Visão Geral

Mapa de Integrações

┌─────────────────────────────────────────────────────────────────┐
│                     SPORT TECH CLUB CORE                        │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │              Integration Layer (ACL)                      │  │
│  │  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐         │  │
│  │  │Adapter │  │Adapter │  │Adapter │  │Adapter │         │  │
│  │  │Ziggy   │  │Payment │  │Keycloak│  │Comms   │   ...   │  │
│  │  └────────┘  └────────┘  └────────┘  └────────┘         │  │
│  └──────────────────────────────────────────────────────────┘  │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                   Event Bus                               │  │
│  │              (RabbitMQ / Redis Pub/Sub)                   │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
         │            │            │            │
         ▼            ▼            ▼            ▼
    ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐
    │ Ziggy  │  │Payment │  │Keycloak│  │WhatsApp│
    │  API   │  │Gateway │  │  SSO   │  │  API   │
    └────────┘  └────────┘  └────────┘  └────────┘

Categorias de Integração

CategoriaQuantidadeCriticidade
Controle de Acesso1CRÍTICA
Pagamentos3CRÍTICA
Identidade & Auth1CRÍTICA
Comunicação4ALTA
Vídeo & Mídia1MÉDIA
IoT & Dispositivos5ALTA
Analytics3BAIXA
Ecosystem2ALTA
CRM/ERP2BAIXA

Princípios Arquiteturais

1. Anti-Corruption Layer (ACL)

Protege o domínio do sistema de mudanças em sistemas externos.

typescript
// domain/integrations/ports/access-control.port.ts
export interface AccessControlPort {
  checkIn(memberId: string, courtId: string): Promise<CheckInResult>;
  checkOut(sessionId: string): Promise<CheckOutResult>;
  getActiveSession(memberId: string): Promise<AccessSession | null>;
}

// infrastructure/integrations/ziggy/ziggy-access-control.adapter.ts
export class ZiggyAccessControlAdapter implements AccessControlPort {
  constructor(
    private readonly httpClient: HttpClient,
    private readonly mapper: ZiggyMapper,
    private readonly config: ZiggyConfig
  ) {}

  async checkIn(memberId: string, courtId: string): Promise<CheckInResult> {
    try {
      // Chamada à API externa do Ziggy
      const response = await this.httpClient.post<ZiggyCheckInResponse>(
        `${this.config.baseUrl}/access/checkin`,
        {
          user_id: memberId,
          court_id: courtId,
          timestamp: new Date().toISOString()
        },
        {
          headers: {
            'Authorization': `Bearer ${this.config.apiKey}`,
            'X-Client-Id': this.config.clientId
          }
        }
      );

      // Traduz resposta externa para modelo de domínio
      return this.mapper.toCheckInResult(response.data);
    } catch (error) {
      throw new AccessControlIntegrationError(
        'Failed to check in via Ziggy',
        error
      );
    }
  }
}

2. Dependency Inversion

typescript
// application/use-cases/check-in-member.use-case.ts
export class CheckInMemberUseCase {
  constructor(
    // Depende de abstração, não de implementação
    private readonly accessControl: AccessControlPort,
    private readonly memberRepo: MemberRepository,
    private readonly eventBus: EventBus
  ) {}

  async execute(command: CheckInCommand): Promise<CheckInResult> {
    const member = await this.memberRepo.findById(command.memberId);
    
    if (!member) {
      throw new MemberNotFoundError(command.memberId);
    }

    if (!member.canCheckIn()) {
      throw new InsufficientCreditsError(member.id);
    }

    // Usa abstração - não sabe que é Ziggy
    const result = await this.accessControl.checkIn(
      member.id,
      command.courtId
    );

    member.deductCredit();
    await this.memberRepo.save(member);

    await this.eventBus.publish(
      new MemberCheckedInEvent(member.id, command.courtId, result.sessionId)
    );

    return result;
  }
}

3. Circuit Breaker Pattern

typescript
// infrastructure/integrations/common/circuit-breaker.ts
export class CircuitBreaker {
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  private failureCount = 0;
  private lastFailureTime?: Date;

  constructor(
    private readonly threshold: number = 5,
    private readonly timeout: number = 60000, // 1 min
    private readonly halfOpenAttempts: number = 3
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (this.shouldAttemptReset()) {
        this.state = 'HALF_OPEN';
      } else {
        throw new CircuitBreakerOpenError('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = new Date();

    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
    }
  }

  private shouldAttemptReset(): boolean {
    if (!this.lastFailureTime) return false;
    return Date.now() - this.lastFailureTime.getTime() > this.timeout;
  }
}

Integrações Implementadas

1. ZIGGY - Sistema de Controle de Acesso

Status: 🟢 Produção
Criticidade: CRÍTICA
Tipo: Síncrona (REST API)
Fornecedor: Ziggy Access Control
SLA: 99.9% uptime

Descrição

Sistema responsável por gerenciar o controle de acesso físico às quadras, incluindo check-in, check-out e rastreamento de consumo em tempo real.

Endpoints

MétodoEndpointDescrição
POST/api/v1/access/checkinRegistra entrada em quadra
POST/api/v1/access/checkoutRegistra saída de quadra
GET/api/v1/access/sessions/activeLista sessões ativas
GET/api/v1/access/sessions/{id}Detalhes de sessão
POST/api/v1/consumption/recordRegistra consumo (bar, loja)
GET/api/v1/consumption/{sessionId}Histórico de consumo

Eventos Recebidos

typescript
// Eventos que o Ziggy envia via webhook
interface ZiggyCheckInEvent {
  event_type: 'access.checkin';
  timestamp: string;
  data: {
    session_id: string;
    user_id: string;
    court_id: string;
    checked_in_at: string;
    device_id: string;
  };
}

interface ZiggyCheckOutEvent {
  event_type: 'access.checkout';
  timestamp: string;
  data: {
    session_id: string;
    checked_out_at: string;
    duration_minutes: number;
    consumption_total: number;
  };
}

interface ZiggyConsumptionEvent {
  event_type: 'consumption.recorded';
  timestamp: string;
  data: {
    session_id: string;
    item_id: string;
    item_name: string;
    quantity: number;
    unit_price: number;
    total_price: number;
  };
}

Adapter Implementation

typescript
// infrastructure/integrations/ziggy/ziggy.adapter.ts
export class ZiggyAdapter implements AccessControlPort {
  private readonly circuitBreaker: CircuitBreaker;
  private readonly retryPolicy: RetryPolicy;

  constructor(
    private readonly httpClient: HttpClient,
    private readonly config: ZiggyConfig,
    private readonly logger: Logger
  ) {
    this.circuitBreaker = new CircuitBreaker(5, 60000);
    this.retryPolicy = new RetryPolicy({
      maxAttempts: 3,
      backoff: 'exponential',
      initialDelay: 1000
    });
  }

  async checkIn(memberId: string, courtId: string): Promise<CheckInResult> {
    return this.circuitBreaker.execute(async () => {
      return this.retryPolicy.execute(async () => {
        this.logger.info('Ziggy check-in', { memberId, courtId });

        const response = await this.httpClient.post<ZiggyCheckInResponse>(
          `${this.config.baseUrl}/access/checkin`,
          {
            user_id: memberId,
            court_id: courtId,
            timestamp: new Date().toISOString()
          },
          {
            headers: this.getAuthHeaders(),
            timeout: 5000
          }
        );

        return {
          sessionId: response.data.session_id,
          checkedInAt: new Date(response.data.checked_in_at),
          court: {
            id: response.data.court.id,
            name: response.data.court.name
          }
        };
      });
    });
  }

  private getAuthHeaders(): Record<string, string> {
    return {
      'Authorization': `Bearer ${this.config.apiKey}`,
      'X-Client-Id': this.config.clientId,
      'Content-Type': 'application/json'
    };
  }
}

2. KEYCLOAK - Identity & Access Management

Status: 🟢 Produção
Criticidade: CRÍTICA
Tipo: OAuth 2.0 / OpenID Connect
Fornecedor: Red Hat Keycloak (Self-hosted)
SLA: 99.99% uptime

Descrição

Sistema de identidade centralizado que gerencia autenticação, autorização, SSO e multi-tenancy para todas as aplicações do ecossistema Sport Tech Club.

Fluxos de Autenticação

Authorization Code Flow (Web/Mobile)
typescript
// infrastructure/integrations/keycloak/keycloak-auth.adapter.ts
export class KeycloakAuthAdapter implements AuthenticationPort {
  async authenticateService(
    clientId: string,
    clientSecret: string
  ): Promise<ServiceToken> {
    const response = await this.httpClient.post<TokenResponse>(
      `${this.config.baseUrl}/realms/${this.config.realm}/protocol/openid-connect/token`,
      new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: clientId,
        client_secret: clientSecret,
        scope: 'openid profile'
      }),
      {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      }
    );

    return {
      accessToken: response.data.access_token,
      expiresIn: response.data.expires_in,
      tokenType: response.data.token_type
    };
  }
}

Token Validation

typescript
// infrastructure/integrations/keycloak/keycloak-token-validator.ts
import * as jwt from 'jsonwebtoken';
import * as jwksClient from 'jwks-rsa';

export class KeycloakTokenValidator {
  private jwksClient: jwksClient.JwksClient;

  constructor(private readonly config: KeycloakConfig) {
    this.jwksClient = jwksClient({
      jwksUri: `${config.baseUrl}/realms/${config.realm}/protocol/openid-connect/certs`,
      cache: true,
      cacheMaxAge: 86400000 // 24 hours
    });
  }

  async validate(token: string): Promise<TokenPayload> {
    try {
      const decoded = jwt.decode(token, { complete: true });
      
      if (!decoded || typeof decoded === 'string') {
        throw new InvalidTokenError('Invalid token format');
      }

      const key = await this.jwksClient.getSigningKey(decoded.header.kid);
      const publicKey = key.getPublicKey();

      const payload = jwt.verify(token, publicKey, {
        issuer: `${this.config.baseUrl}/realms/${this.config.realm}`,
        audience: this.config.clientId,
        algorithms: ['RS256']
      }) as jwt.JwtPayload;

      return {
        sub: payload.sub!,
        email: payload.email,
        name: payload.name,
        roles: payload.realm_access?.roles || [],
        tenantId: payload.tenant_id,
        expiresAt: new Date(payload.exp! * 1000)
      };
    } catch (error) {
      if (error instanceof jwt.TokenExpiredError) {
        throw new TokenExpiredError();
      }
      throw new InvalidTokenError(error.message);
    }
  }
}

3. PAYMENT GATEWAYS - Processamento de Pagamentos

Status: 🟡 Implementação Parcial
Criticidade: CRÍTICA
Tipo: Síncrona (REST API) + Webhooks
Fornecedores: Stripe, Mercado Pago, PagSeguro

Strategy Pattern para Múltiplos Gateways

typescript
// domain/integrations/ports/payment-gateway.port.ts
export interface PaymentGatewayPort {
  createPayment(command: CreatePaymentCommand): Promise<PaymentResult>;
  capturePayment(paymentId: string): Promise<CaptureResult>;
  refundPayment(paymentId: string, amount?: number): Promise<RefundResult>;
  createSubscription(command: CreateSubscriptionCommand): Promise<Subscription>;
  cancelSubscription(subscriptionId: string): Promise<void>;
  getPaymentStatus(paymentId: string): Promise<PaymentStatus>;
}

// infrastructure/integrations/payment/payment-gateway.factory.ts
export class PaymentGatewayFactory {
  constructor(
    private readonly stripeAdapter: StripeAdapter,
    private readonly mercadoPagoAdapter: MercadoPagoAdapter,
    private readonly pagSeguroAdapter: PagSeguroAdapter
  ) {}

  create(type: PaymentGatewayType): PaymentGatewayPort {
    switch (type) {
      case 'STRIPE':
        return this.stripeAdapter;
      case 'MERCADO_PAGO':
        return this.mercadoPagoAdapter;
      case 'PAGSEGURO':
        return this.pagSeguroAdapter;
      default:
        throw new UnsupportedGatewayError(type);
    }
  }
}

Stripe Integration

typescript
// infrastructure/integrations/payment/stripe/stripe.adapter.ts
import Stripe from 'stripe';

export class StripeAdapter implements PaymentGatewayPort {
  private readonly stripe: Stripe;

  constructor(private readonly config: StripeConfig) {
    this.stripe = new Stripe(config.secretKey, {
      apiVersion: '2023-10-16',
      typescript: true
    });
  }

  async createPayment(command: CreatePaymentCommand): Promise<PaymentResult> {
    const paymentIntent = await this.stripe.paymentIntents.create({
      amount: Math.round(command.amount * 100), // Centavos
      currency: command.currency.toLowerCase(),
      customer: command.customerId,
      description: command.description,
      metadata: command.metadata,
      automatic_payment_methods: {
        enabled: true
      }
    });

    return {
      id: paymentIntent.id,
      status: this.mapStatus(paymentIntent.status),
      amount: paymentIntent.amount / 100,
      clientSecret: paymentIntent.client_secret!,
      createdAt: new Date(paymentIntent.created * 1000)
    };
  }

  async createSubscription(
    command: CreateSubscriptionCommand
  ): Promise<Subscription> {
    const subscription = await this.stripe.subscriptions.create({
      customer: command.customerId,
      items: [{ price: command.priceId }],
      metadata: command.metadata,
      payment_behavior: 'default_incomplete',
      payment_settings: {
        save_default_payment_method: 'on_subscription'
      }
    });

    return {
      id: subscription.id,
      status: this.mapSubscriptionStatus(subscription.status),
      currentPeriodStart: new Date(subscription.current_period_start * 1000),
      currentPeriodEnd: new Date(subscription.current_period_end * 1000)
    };
  }
}

4. COMMUNICATION CHANNELS - Canais de Comunicação

Status: 🟡 Implementação Parcial
Criticidade: ALTA
Tipo: Assíncrona (Webhooks) + API REST

WhatsApp Business API (Twilio)

typescript
// infrastructure/integrations/communication/whatsapp/twilio-whatsapp.adapter.ts
import twilio from 'twilio';

export class TwilioWhatsAppAdapter implements CommunicationPort {
  private readonly client: twilio.Twilio;

  constructor(private readonly config: TwilioConfig) {
    this.client = twilio(config.accountSid, config.authToken);
  }

  async sendMessage(command: SendMessageCommand): Promise<MessageResult> {
    const to = `whatsapp:${this.formatPhoneNumber(command.recipient)}`;
    const from = `whatsapp:${this.config.phoneNumber}`;

    const message = await this.client.messages.create({
      to,
      from,
      body: command.content.text,
      ...(command.content?.mediaUrl && { mediaUrl: [command.content.mediaUrl] })
    });

    return {
      messageId: message.sid,
      status: this.mapStatus(message.status),
      sentAt: new Date(message.dateCreated)
    };
  }

  private formatPhoneNumber(phone: string): string {
    let cleaned = phone.replace(/\D/g, '');
    if (!cleaned.startsWith('55')) {
      cleaned = '55' + cleaned;
    }
    return '+' + cleaned;
  }
}

Push Notifications (Firebase FCM)

typescript
// infrastructure/integrations/communication/push/firebase-fcm.adapter.ts
import * as admin from 'firebase-admin';

export class FirebaseFCMAdapter implements CommunicationPort {
  private readonly messaging: admin.messaging.Messaging;

  constructor(config: FirebaseConfig) {
    const app = admin.initializeApp({
      credential: admin.credential.cert(config.serviceAccount)
    });
    this.messaging = admin.messaging(app);
  }

  async sendMessage(command: SendMessageCommand): Promise<MessageResult> {
    const message: admin.messaging.Message = {
      token: command.recipient,
      notification: {
        title: command.content!.title,
        body: command.content!.text,
        imageUrl: command.content?.mediaUrl
      },
      data: command.metadata,
      android: {
        priority: 'high',
        notification: {
          channelId: 'default',
          sound: 'default'
        }
      }
    };

    const messageId = await this.messaging.send(message);

    return {
      messageId,
      status: MessageStatus.SENT,
      sentAt: new Date()
    };
  }
}

5. VIDEO SYSTEM - Sistema de Vídeo e Highlights

Status: 🔴 Planejado
Criticidade: MÉDIA
Tipo: Webhook + Streaming API
Fornecedor: A definir (Mux, Cloudflare Stream, AWS IVS)

Port & Adapter

typescript
// domain/integrations/ports/video-platform.port.ts
export interface VideoPlatformPort {
  createLiveStream(command: CreateLiveStreamCommand): Promise<LiveStream>;
  stopLiveStream(streamId: string): Promise<void>;
  createHighlight(command: CreateHighlightCommand): Promise<Highlight>;
  getVideoAsset(assetId: string): Promise<VideoAsset>;
  getPlaybackUrl(assetId: string): Promise<string>;
  deleteVideo(assetId: string): Promise<void>;
}

// infrastructure/integrations/video/mux/mux-video.adapter.ts
import Mux from '@mux/mux-node';

export class MuxVideoAdapter implements VideoPlatformPort {
  private readonly mux: Mux;

  constructor(config: MuxConfig) {
    this.mux = new Mux(config.tokenId, config.tokenSecret);
  }

  async createLiveStream(
    command: CreateLiveStreamCommand
  ): Promise<LiveStream> {
    const liveStream = await this.mux.video.liveStreams.create({
      playback_policy: ['public'],
      new_asset_settings: {
        playback_policy: ['public']
      },
      reconnect_window: 60,
      latency_mode: 'low',
      passthrough: JSON.stringify({
        courtId: command.courtId,
        sessionId: command.sessionId
      })
    });

    return {
      id: liveStream.id,
      streamKey: liveStream.stream_key!,
      playbackId: liveStream.playback_ids![0].id,
      status: 'idle',
      hlsUrl: `https://stream.mux.com/${liveStream.playback_ids![0].id}.m3u8`
    };
  }
}

Physical Button Integration (IoT)

typescript
// infrastructure/integrations/iot/highlight-button.handler.ts
export class HighlightButtonHandler {
  constructor(
    private readonly mqttClient: MqttClient,
    private readonly videoService: VideoService,
    private readonly sessionRepo: SessionRepository
  ) {
    this.subscribeToButtonEvents();
  }

  private subscribeToButtonEvents(): void {
    this.mqttClient.subscribe('court/+/button/pressed', async (topic, message) => {
      const courtId = this.extractCourtId(topic);
      const buttonData = JSON.parse(message.toString());
      await this.handleButtonPress(courtId, buttonData);
    });
  }

  private async handleButtonPress(
    courtId: string,
    data: ButtonPressData
  ): Promise<void> {
    const session = await this.sessionRepo.findActiveByCourtId(courtId);

    if (!session || !session.liveStreamId) {
      console.warn('No active session with live stream for court', courtId);
      return;
    }

    const now = Date.now();
    const streamStartTime = session.streamStartedAt.getTime();
    const relativeTime = (now - streamStartTime) / 1000;

    await this.videoService.createHighlight({
      liveStreamId: session.liveStreamId,
      startTime: Math.max(0, relativeTime - 10),
      duration: 30,
      sessionId: session.id,
      courtId,
      triggeredBy: 'button'
    });
  }
}

6. IOT DEVICES - Dispositivos e Sensores

Status: 🟡 Implementação Parcial
Criticidade: ALTA
Tipo: MQTT + HTTP
Protocolos: MQTT, WebSocket, HTTP REST

MQTT Broker Architecture

typescript
// infrastructure/integrations/iot/mqtt-broker.service.ts
import * as mqtt from 'mqtt';

export class MqttBrokerService extends EventEmitter {
  private client: mqtt.MqttClient;

  constructor(private readonly config: MqttConfig) {
    super();
    this.connect();
  }

  private connect(): void {
    this.client = mqtt.connect(this.config.brokerUrl, {
      clientId: `sport-tech-club-${Math.random().toString(16).slice(2, 8)}`,
      username: this.config.username,
      password: this.config.password,
      clean: true,
      reconnectPeriod: 1000
    });

    this.client.on('connect', () => {
      console.log('Connected to MQTT broker');
      this.subscribeToTopics();
    });

    this.client.on('message', (topic, payload) => {
      this.handleMessage(topic, payload);
    });
  }

  private subscribeToTopics(): void {
    const topics = [
      'court/+/checkin',
      'court/+/checkout',
      'court/+/button/pressed',
      'tablet/+/status',
      'rfid/+/scan'
    ];

    topics.forEach(topic => {
      this.client.subscribe(topic, { qos: 1 });
    });
  }
}

RFID/NFC Reader Integration

typescript
// infrastructure/integrations/iot/rfid-reader.adapter.ts
export class RfidReaderAdapter {
  constructor(
    private readonly mqttBroker: MqttBrokerService,
    private readonly memberRepo: MemberRepository,
    private readonly accessControl: AccessControlPort
  ) {
    this.setupListeners();
  }

  private async handleRfidScan(msg: MqttMessage): Promise<void> {
    const { card_id, reader_id } = msg.data;

    const member = await this.memberRepo.findByRfidCard(card_id);

    if (!member || !member.isActive()) {
      await this.sendReaderResponse(reader_id, {
        status: 'denied',
        message: 'Acesso negado',
        color: 'red'
      });
      return;
    }

    const courtId = this.getCourtIdByReader(reader_id);

    try {
      await this.accessControl.checkIn(member.id, courtId);

      await this.sendReaderResponse(reader_id, {
        status: 'granted',
        message: `Bem-vindo, ${member.firstName}!`,
        color: 'green'
      });
    } catch (error) {
      await this.sendReaderResponse(reader_id, {
        status: 'denied',
        message: 'Erro ao registrar entrada',
        color: 'red'
      });
    }
  }
}

7. ANTÓCTICA ECOSYSTEM - Integrações Internas

Status: 🟡 Planejamento
Criticidade: ALTA
Tipo: Event-Driven + REST API

Shared Wallet Integration

typescript
// domain/integrations/ports/wallet.port.ts
export interface WalletPort {
  getBalance(userId: string): Promise<WalletBalance>;
  createTransaction(command: CreateTransactionCommand): Promise<Transaction>;
  transferBetweenApps(command: TransferCommand): Promise<Transfer>;
  getTransactionHistory(userId: string): Promise<Transaction[]>;
}

// infrastructure/integrations/antoctica/wallet.adapter.ts
export class AntocticaWalletAdapter implements WalletPort {
  async getBalance(userId: string): Promise<WalletBalance> {
    const token = await this.authService.getServiceToken();

    const response = await this.httpClient.get<WalletBalanceResponse>(
      `${this.config.walletApiUrl}/users/${userId}/balance`,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'X-Service-Id': 'sport-tech-club'
        }
      }
    );

    return {
      available: response.data.available,
      pending: response.data.pending,
      currency: response.data.currency
    };
  }

  async transferBetweenApps(command: TransferCommand): Promise<Transfer> {
    const token = await this.authService.getServiceToken();

    const response = await this.httpClient.post<TransferResponse>(
      `${this.config.walletApiUrl}/transfers/inter-app`,
      {
        from_app: 'sport-tech-club',
        to_app: command.targetApp,
        user_id: command.userId,
        amount: command.amount,
        reason: command.reason
      },
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'X-Service-Id': 'sport-tech-club'
        }
      }
    );

    return this.mapToTransfer(response.data);
  }
}

Integrações Planejadas

8. ANALYTICS PLATFORMS

Status: 🔴 Planejado
Providers: Google Analytics 4, Mixpanel, Amplitude

typescript
// domain/integrations/ports/analytics.port.ts
export interface AnalyticsPort {
  trackEvent(event: AnalyticsEvent): Promise<void>;
  identifyUser(userId: string, traits: UserTraits): Promise<void>;
  trackPageView(page: PageView): Promise<void>;
}

9. CRM INTEGRATION

Status: 🔴 Planejado
Providers: HubSpot, RD Station

typescript
// domain/integrations/ports/crm.port.ts
export interface CrmPort {
  createContact(contact: Contact): Promise<string>;
  updateContact(contactId: string, updates: Partial<Contact>): Promise<void>;
  createDeal(deal: Deal): Promise<string>;
  trackActivity(activity: Activity): Promise<void>;
}

10. ERP INTEGRATION

Status: 🔴 Planejado
Purpose: Integração financeira e contábil

typescript
// domain/integrations/ports/erp.port.ts
export interface ErpPort {
  syncInvoice(invoice: Invoice): Promise<void>;
  syncPayment(payment: Payment): Promise<void>;
  getAccountBalance(accountId: string): Promise<AccountBalance>;
}

Padrões de Integração

Retry Policy

typescript
// infrastructure/integrations/common/retry-policy.ts
export class RetryPolicy {
  constructor(private readonly config: RetryConfig) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    let lastError: Error;
    
    for (let attempt = 1; attempt <= this.config.maxAttempts; attempt++) {
      try {
        return await fn();
      } catch (error) {
        lastError = error as Error;
        
        if (attempt === this.config.maxAttempts) {
          throw lastError;
        }

        if (!this.isRetriable(error)) {
          throw error;
        }

        const delay = this.calculateDelay(attempt);
        await this.sleep(delay);
      }
    }

    throw lastError!;
  }

  private calculateDelay(attempt: number): number {
    switch (this.config.backoff) {
      case 'exponential':
        return this.config.initialDelay * Math.pow(2, attempt - 1);
      case 'linear':
        return this.config.initialDelay * attempt;
      default:
        return this.config.initialDelay;
    }
  }

  private isRetriable(error: any): boolean {
    if (error.statusCode >= 400 && error.statusCode < 500) {
      return error.statusCode === 429;
    }
    return true;
  }
}

Dead Letter Queue

typescript
// infrastructure/integrations/common/dead-letter-queue.ts
export class DeadLetterQueue {
  constructor(
    private readonly repository: FailedIntegrationRepository,
    private readonly alertService: AlertService
  ) {}

  async enqueue(failure: IntegrationFailure): Promise<void> {
    await this.repository.save({
      id: crypto.randomUUID(),
      integration: failure.integration,
      operation: failure.operation,
      payload: failure.payload,
      error: failure.error,
      attempts: failure.attempts,
      failedAt: new Date(),
      status: 'pending_retry'
    });

    if (failure.isCritical) {
      await this.alertService.sendAlert({
        severity: 'high',
        title: `Integration Failure: ${failure.integration}`,
        description: failure.error.message
      });
    }
  }
}

Idempotency

typescript
// infrastructure/integrations/common/idempotency.middleware.ts
export class IdempotencyMiddleware {
  constructor(private readonly cache: CacheService) {}

  async execute<T>(
    key: string,
    fn: () => Promise<T>,
    ttl: number = 86400
  ): Promise<T> {
    const cached = await this.cache.get<T>(key);
    
    if (cached) {
      return cached;
    }

    const result = await fn();
    await this.cache.set(key, result, ttl);

    return result;
  }
}

Segurança

API Authentication

typescript
// infrastructure/integrations/common/auth/service-auth.service.ts
export class ServiceAuthService {
  private tokenCache = new Map<string, CachedToken>();

  async getServiceToken(scope?: string): Promise<string> {
    const cacheKey = scope || 'default';
    const cached = this.tokenCache.get(cacheKey);

    if (cached && !this.isTokenExpired(cached)) {
      return cached.token;
    }

    const token = await this.keycloak.authenticateService(
      this.config.clientId,
      this.config.clientSecret
    );

    this.tokenCache.set(cacheKey, {
      token: token.accessToken,
      expiresAt: new Date(Date.now() + token.expiresIn * 1000)
    });

    return token.accessToken;
  }

  private isTokenExpired(cached: CachedToken): boolean {
    return cached.expiresAt.getTime() - Date.now() < 300000;
  }
}

Request Signing

typescript
// infrastructure/integrations/common/request-signer.ts
import * as crypto from 'crypto';

export class RequestSigner {
  constructor(private readonly secret: string) {}

  sign(payload: any, timestamp: number = Date.now()): string {
    const message = `${timestamp}.${JSON.stringify(payload)}`;
    
    return crypto
      .createHmac('sha256', this.secret)
      .update(message)
      .digest('hex');
  }

  verify(payload: any, signature: string, timestamp: number): boolean {
    if (Date.now() - timestamp > 300000) {
      return false;
    }

    const expectedSignature = this.sign(payload, timestamp);
    
    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
  }
}

Resiliência e Confiabilidade

Health Checks

typescript
// infrastructure/integrations/common/health-check.service.ts
export class IntegrationHealthCheckService {
  constructor(
    private readonly integrations: Map<string, HealthCheckable>
  ) {}

  async checkAll(): Promise<HealthCheckResult[]> {
    const results = await Promise.allSettled(
      Array.from(this.integrations.entries()).map(async ([name, integration]) => {
        const start = Date.now();
        
        try {
          await integration.healthCheck();
          
          return {
            name,
            status: 'healthy',
            latency: Date.now() - start
          };
        } catch (error) {
          return {
            name,
            status: 'unhealthy',
            error: error.message,
            latency: Date.now() - start
          };
        }
      })
    );

    return results.map(r => r.status === 'fulfilled' ? r.value : r.reason);
  }
}

Fallback Strategies

typescript
// application/services/notification-with-fallback.service.ts
export class NotificationWithFallbackService {
  async sendWithFallback(command: SendNotificationCommand): Promise<void> {
    const strategies: CommunicationChannel[] = [
      command.channel,
      ...this.getFallbackChannels(command.channel)
    ];

    for (const channel of strategies) {
      try {
        await this.notificationService.sendNotification({
          ...command,
          channel
        });
        return;
      } catch (error) {
        console.warn(`Failed via ${channel}, trying next`, error);
      }
    }

    throw new AllChannelsFailedError('All channels failed');
  }
}

Monitoramento

Integration Metrics

typescript
// infrastructure/integrations/common/metrics.service.ts
import { Histogram, Counter, Gauge } from 'prom-client';

export class IntegrationMetricsService {
  private readonly requestDuration: Histogram;
  private readonly requestTotal: Counter;
  private readonly requestErrors: Counter;

  constructor() {
    this.requestDuration = new Histogram({
      name: 'integration_request_duration_seconds',
      help: 'Duration of integration requests',
      labelNames: ['integration', 'operation', 'status']
    });

    this.requestTotal = new Counter({
      name: 'integration_requests_total',
      help: 'Total number of integration requests',
      labelNames: ['integration', 'operation']
    });

    this.requestErrors = new Counter({
      name: 'integration_errors_total',
      help: 'Total number of integration errors',
      labelNames: ['integration', 'operation', 'error_type']
    });
  }

  recordRequest(
    integration: string,
    operation: string,
    duration: number,
    success: boolean
  ): void {
    this.requestTotal.inc({ integration, operation });
    
    this.requestDuration.observe(
      { integration, operation, status: success ? 'success' : 'error' },
      duration / 1000
    );
  }
}

Logging

typescript
// infrastructure/integrations/common/integration-logger.ts
export class IntegrationLogger {
  constructor(
    private readonly logger: Logger,
    private readonly integration: string
  ) {}

  logRequest(operation: string, payload: any): void {
    this.logger.info('Integration request', {
      integration: this.integration,
      operation,
      payload: this.sanitize(payload)
    });
  }

  logError(operation: string, error: Error): void {
    this.logger.error('Integration error', {
      integration: this.integration,
      operation,
      error: {
        message: error.message,
        stack: error.stack
      }
    });
  }

  private sanitize(data: any): any {
    const sensitiveKeys = ['password', 'apiKey', 'secret', 'token'];

    if (typeof data !== 'object' || data === null) {
      return data;
    }

    const sanitized = { ...data };

    for (const key of Object.keys(sanitized)) {
      if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) {
        sanitized[key] = '[REDACTED]';
      }
    }

    return sanitized;
  }
}

Conclusão

Esta documentação fornece uma visão completa da arquitetura de integrações do Sport Tech Club, seguindo os princípios de:

Clean Architecture - Separação de camadas e dependências invertidas
Domain-Driven Design - Foco no domínio, com ACL protegendo o core
Resiliência - Circuit breakers, retries, fallbacks
Observabilidade - Logs, métricas, health checks
Segurança - Autenticação, autorização, secrets management
Extensibilidade - Fácil adicionar novas integrações via Port/Adapter

Próximos Passos

  1. Implementar integrações prioritárias (Ziggy, Keycloak, Payments)
  2. Configurar monitoramento e alertas
  3. Criar testes de integração end-to-end
  4. Documentar runbooks para troubleshooting
  5. Estabelecer SLAs com fornecedores externos

Mantido por: Time de Arquitetura - Sport Tech Club
Última Atualização: 2026-01-09