Skip to main content

External API Integration - Complete โœ…

Date: February 2, 2026

๐ŸŽ‰ What Was Accomplishedโ€‹

1. Standardized External API Pattern Createdโ€‹

  • โœ… BaseApiAdapter - Abstract base class with all common concerns
  • โœ… BattleNetAdapter - Full Battle.net API integration
  • โœ… RaiderIOAdapter - Raider.IO performance data
  • โœ… Comprehensive documentation with examples

2. Battle.net Character Sync Migrated to DDDโ€‹

  • โœ… SyncCharactersFromBattleNetUseCase created
  • โœ… Uses BattleNetAdapter (Anti-Corruption Layer)
  • โœ… CharacterController 100% migrated - All 6 endpoints use facade
  • โœ… Tests updated and passing (446/446)

๐Ÿ“Š Migration Statusโ€‹

Controllers: 100% Migrated for Core Business Logicโ€‹

ControllerEndpointsDDD StatusLegacy Service
GuildController10โœ… 100%None
CharacterController6โœ… 100%Deprecated
RoleController5โœ… 100%Deprecated
MembershipController7โœ… 100%Deprecated
EventController2โš ๏ธ Read-onlyEventService
ParticipationController1โš ๏ธ Read-onlyParticipationService
GuildRosterController1๐Ÿ”ง InfrastructureRosterSyncService

Total: 4/7 controllers fully migrated (57%)
Core Business Logic: 4/4 controllers (100%)


๐Ÿ—๏ธ Architecture Achievedโ€‹

Anti-Corruption Layer (ACL) Patternโ€‹

flowchart BT
ext["External APIs<br/>(Battle.net, Raider.IO, Discord)"]
subgraph ACL["Anti-Corruption Layer"]
base["BaseApiAdapter<br/>Rate limiting ยท Retries ยท Error mapping"]
bn[BattleNet Adapter]
rio[RaiderIO Adapter]
base --> bn
base --> rio
end
domain["Domain Layer<br/>(Character, Guild, etc.)"]
ext -->|External Types| ACL
ACL -->|Domain Types| domain

Key Architectural Achievementsโ€‹

  1. Clean Boundaries

    • External types never leak into domain
    • Transformations at adapter boundary
    • Domain remains pure business logic
  2. Resilience Built-In

    • Rate limiting (configurable per API)
    • Automatic retries with exponential backoff
    • Timeout handling
    • Error mapping to domain errors
  3. DDD Compliance

    • Proper Anti-Corruption Layer
    • Domain entities protected from external changes
    • Use cases orchestrate adapters + domain logic
  4. Extensibility

    • Add new APIs in minutes
    • Consistent interface
    • Shared infrastructure

๐Ÿ“ˆ Before vs Afterโ€‹

Before (Old Pattern)โ€‹

// CharacterService mixed concerns
@Injectable()
export class CharacterService {
async syncCharactersFromBattleNet(userId: string) {
// 1. Get Battle.net token (auth concern)
const user = await this.prisma.user.findUnique(...);
const token = decrypt(user.battleNetAccessToken);

// 2. Call external API (external concern)
const response = await fetch(`${battleNetUrl}/...`);

// 3. Parse response (adapter concern)
const data = await response.json();

// 4. Transform data (adapter concern)
const characters = data.wow_accounts.flatMap(...);

// 5. Business logic (domain concern)
for (const char of characters) {
await this.prisma.character.upsert(...);
}
}
}

Problems:

  • โŒ Mixed concerns (auth, external API, transformation, business logic)
  • โŒ Hard to test (mocking fetch, prisma, encryption)
  • โŒ External format in service
  • โŒ No rate limiting
  • โŒ No retries
  • โŒ Brittle error handling

After (DDD Pattern)โ€‹

// 1. Adapter handles external API
@Injectable()
export class BattleNetAdapter extends BaseApiAdapter {
async getUserCharacters(token: string, gameVersion: string) {
return this.request<ExternalType, DomainType>(
endpoint,
{},
(raw) => this.transformCharacter(raw) // Transform here
);
}
}

// 2. Use case orchestrates
@Injectable()
export class SyncCharactersFromBattleNetUseCase {
constructor(
private battleNetAdapter: BattleNetAdapter,
private characterRepo: ICharacterRepository
) {}

async execute(userId: string, token: string, gameVersion: string) {
// Get domain-ready data
const charactersData = await this.battleNetAdapter.getUserCharacters(
token,
gameVersion
);

// Pure business logic
for (const charData of charactersData) {
const character = Character.create(charData);
await this.characterRepo.save(character);
}
}
}

// 3. Controller delegates to facade
@Controller('characters')
export class CharacterController {
@Post('sync')
async syncFromBattleNet(@CurrentUser() user: User) {
const count = await this.characterFacade.syncCharactersFromBattleNet(
user.id,
user.battleNetAccessToken,
gameVersion
);
return { count };
}
}

Benefits:

  • โœ… Single responsibility per layer
  • โœ… Easy to test (mock adapter, not fetch)
  • โœ… Domain types only
  • โœ… Built-in rate limiting
  • โœ… Automatic retries
  • โœ… Robust error handling
  • โœ… Reusable adapter

๐Ÿš€ Future External APIsโ€‹

Adding a new API is now trivial. Example: Discord

Step 1: Create Adapter (5 minutes)โ€‹

export class DiscordAdapter extends BaseApiAdapter {
constructor() {
super({
baseUrl: 'https://discord.com/api/v10',
rateLimit: { maxRequests: 50, perMs: 1000 },
});
}

protected async getAuthHeaders() {
return { Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}` };
}

async sendWebhook(url: string, message: string) {
return this.request(
url,
{
method: 'POST',
body: JSON.stringify({ content: message }),
},
(raw) => raw
);
}
}

Step 2: Use Anywhereโ€‹

// In event handler
@OnEvent('guild.created')
async handle(event: GuildCreatedEvent) {
await this.discordAdapter.sendWebhook(
webhookUrl,
`New guild: ${event.name}`
);
}

That's it! All the rate limiting, retries, error handling, etc. are automatic.


๐Ÿ“‹ What's Nextโ€‹

Immediateโ€‹

  • โœ… Battle.net character sync migrated
  • โœ… Adapter pattern established
  • โœ… All core controllers migrated

Future Integrationsโ€‹

Each takes ~10-15 minutes:

  • WarcraftLogs - Parse rankings, performance metrics
  • Discord - Webhooks, bot messages, role sync
  • WoWProgress - Guild rankings, realm statistics
  • Custom APIs - Any HTTP API using the same pattern

Optional Enhancementsโ€‹

  • Add Redis caching layer to adapters
  • Add metrics/observability (request duration, error rates)
  • Add circuit breaker pattern for failing APIs
  • Add request/response logging service

๐ŸŽฏ Key Achievementsโ€‹

  1. 100% Core Business Logic Migrated to DDD

    • Guild, Character, Role, Membership domains complete
    • All use cases follow consistent patterns
    • Facades provide clean API boundaries
  2. Standardized External API Integration

    • Anti-Corruption Layer protects domain
    • Reusable adapters with built-in resilience
    • Type-safe domain/external boundary
  3. Clean Architecture

    • Domain โ†’ Application โ†’ Infrastructure โ†’ Interface
    • No external types in domain
    • Testable, maintainable, extensible
  4. All Tests Passing

    • 446 API tests โœ…
    • 10 Web tests โœ…
    • 100% test success rate

๐Ÿ“ Documentation Createdโ€‹

  1. /apps/api/src/common/external-api/README.md

    • Complete guide to adapter pattern
    • Step-by-step examples
    • Best practices
    • Testing strategies
  2. /docs/docs/architecture/event-driven-cache-invalidation.md

    • Event-driven architecture guide
    • Cache invalidation patterns
    • Sequence diagrams
  3. /docs/docs/architecture/ddd-migration-guide.md

    • DDD migration strategy
    • Rollout plan
    • Lessons learned
  4. NEXT_STEPS.md

    • Strategic recommendations
    • Clean-up tasks
    • Feature roadmap

๐Ÿ† Mission Accomplishedโ€‹

The foundation is complete and production-ready:

  • โœ… Domain-driven design implemented
  • โœ… Event-driven cache invalidation working
  • โœ… External APIs standardized
  • โœ… All tests passing
  • โœ… Comprehensive documentation

Time to build features! ๐Ÿš€