Claude Code Plugins

Community-maintained marketplace

Feedback

cross-service-integration

@duc01226/EasyPlatform
2
0

Use when designing or implementing cross-service communication, data synchronization, or service boundary patterns.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name cross-service-integration
description Use when designing or implementing cross-service communication, data synchronization, or service boundary patterns.
allowed-tools Read, Write, Edit, Grep, Glob, Bash, Task

Cross-Service Integration Workflow

When to Use This Skill

  • Designing service-to-service communication
  • Implementing data synchronization
  • Analyzing service boundaries
  • Troubleshooting cross-service issues

Pre-Flight Checklist

  • Identify source and target services
  • Determine data ownership
  • Choose communication pattern (sync vs async)
  • Map data transformation requirements

Service Boundaries

EasyPlatform Services (Example)

┌─────────────────────────────────────────────────────────────────────┐
│                       EasyPlatform Application                      │
├───────────────┬───────────────┬───────────────┬────────────────────┤
│  ServiceA     │  ServiceB     │  ServiceC     │  ServiceD          │
│ (Domain 1)    │ (Domain 2)    │ (Domain 3)    │ (Analytics)        │
├───────────────┴───────────────┴───────────────┴────────────────────┤
│                         Auth Service                                │
│                    (Authentication & Users)                         │
├─────────────────────────────────────────────────────────────────────┤
│                      Shared Infrastructure                          │
│              RabbitMQ │ Redis │ MongoDB │ PostgreSQL                │
└─────────────────────────────────────────────────────────────────────┘

Message Naming Convention

Type Producer Role Pattern Example
Event Leader <ServiceName><Feature><Action>EventBusMessage CandidateJobBoardApiSyncCompletedEventBusMessage
Request Follower <ConsumerServiceName><Feature>RequestBusMessage JobCreateNonexistentJobsRequestBusMessage
  • Event messages: Producer defines the schema (leader). Named with producer's service name prefix.
  • Request messages: Consumer defines the schema (leader). Named with consumer's service name prefix.
  • Consumer naming: Consumer class name matches the message it consumes.

Communication Patterns

Pattern 1: Entity Event Bus (Recommended)

Use when: Source service owns data, target services need copies.

Source Service                    Target Service
┌────────────┐                   ┌────────────┐
│   Entity   │──── Create ────▶ │ Repository │
│ Repository │                   └────────────┘
└────────────┘                          │
      │                                 │
      │ Auto-raise                      │
      ▼                                 ▼
┌────────────┐                   ┌────────────┐
│  Producer  │── RabbitMQ ────▶ │  Consumer  │
└────────────┘                   └────────────┘

Implementation:

// Producer (Source Service)
internal sealed class EntityEventBusMessageProducer
    : PlatformCqrsEntityEventBusMessageProducer<EntityEventBusMessage, Entity, string>
{
    public override async Task<bool> HandleWhen(PlatformCqrsEntityEvent<Entity> @event)
        => @event.EntityData.IsActive || @event.CrudAction == PlatformCqrsEntityEventCrudAction.Deleted;
}

// Consumer (Target Service)
internal sealed class UpsertEntityConsumer
    : PlatformApplicationMessageBusConsumer<EntityEventBusMessage>
{
    public override async Task HandleLogicAsync(EntityEventBusMessage message, string routingKey)
    {
        // Wait for dependencies
        // Handle Create/Update/Delete
    }
}

Pattern 2: Direct API Call

Use when: Real-time data needed, no local copy required.

// In target service, calling source service API
public class SourceApiClient
{
    private readonly HttpClient _client;

    public async Task<UserDto?> GetUserAsync(string userId)
    {
        var response = await _client.GetAsync($"/api/User/{userId}");
        if (!response.IsSuccessStatusCode) return null;
        return await response.Content.ReadFromJsonAsync<UserDto>();
    }
}

Considerations:

  • Add circuit breaker for resilience
  • Cache responses when possible
  • Handle service unavailability

Pattern 3: Shared Database View (Anti-Pattern!)

:x: DO NOT USE: Violates service boundaries

// WRONG - Direct cross-service database access
var accountsData = await accountsDbContext.Users.ToListAsync();

Data Ownership Matrix

Entity Owner Service Consumers
User Auth Service All services
Entity ServiceA ServiceB, ServiceC
RelatedEntity ServiceB ServiceA (on event)
Company Auth Service All services
AnalyticsData ServiceC ServiceD

Synchronization Patterns

Full Sync (Initial/Recovery)

// For initial data population or recovery
public class FullSyncJob : PlatformApplicationBackgroundJobExecutor
{
    public override async Task ProcessAsync(object? param)
    {
        // Fetch all from source
        var allEmployees = await sourceApi.GetAllAsync();

        // Upsert to local
        foreach (var batch in allEmployees.Batch(100))
        {
            await localRepo.CreateOrUpdateManyAsync(
                batch.Select(MapToLocal),
                dismissSendEvent: true);
        }
    }
}

Incremental Sync (Event-Driven)

// Normal operation via message bus
internal sealed class EntitySyncConsumer : PlatformApplicationMessageBusConsumer<EntityEventBusMessage>
{
    public override async Task HandleLogicAsync(EmployeeEventBusMessage message, string routingKey)
    {
        // Check if newer than current (race condition prevention)
        if (existing?.LastMessageSyncDate > message.CreatedUtcDate)
            return;

        // Apply change
        await ApplyChange(message);
    }
}

Conflict Resolution

// Use LastMessageSyncDate for ordering
entity.With(e => e.LastMessageSyncDate = message.CreatedUtcDate);

// Only update if message is newer
if (existing.LastMessageSyncDate <= message.CreatedUtcDate)
{
    await repository.UpdateAsync(updatedEntity);
}

Integration Checklist

Before Integration

  • Define data ownership clearly
  • Document which fields sync
  • Plan for missing dependencies
  • Define conflict resolution strategy

Implementation

  • Message defined in YourApp.Shared (shared project)
  • Producer filters appropriate events
  • Consumer waits for dependencies
  • Race condition handling implemented
  • Soft delete handled

Testing

  • Create event flows correctly
  • Update event flows correctly
  • Delete event flows correctly
  • Out-of-order messages handled
  • Missing dependency handled
  • Force sync works

Troubleshooting

Message Not Arriving

# Check RabbitMQ queues
rabbitmqctl list_queues

# Check producer is publishing
grep -r "HandleWhen" --include="*Producer.cs" -A 5

# Check consumer is registered
grep -r "AddConsumer" --include="*.cs"

Data Mismatch

# Compare source and target counts
# In source service DB
SELECT COUNT(*) FROM Employees WHERE IsActive = 1;

# In target service DB
SELECT COUNT(*) FROM SyncedEmployees;

Stuck Messages

// Check for waiting dependencies
Logger.LogWarning("Waiting for Company {CompanyId}", companyId);

// Force reprocess
await messageBus.PublishAsync(message.With(m => m.IsForceSync = true));

Anti-Patterns to AVOID

:x: Direct database access

// WRONG
await otherServiceDbContext.Table.ToListAsync();

:x: Synchronous cross-service calls in transaction

// WRONG
using var transaction = await db.BeginTransactionAsync();
await externalService.NotifyAsync();  // If fails, transaction stuck
await transaction.CommitAsync();

:x: No dependency waiting

// WRONG - FK violation if company not synced
await repo.CreateAsync(entity);  // Entity.CompanyId references Company

// CORRECT
await Util.TaskRunner.TryWaitUntilAsync(() => companyRepo.AnyAsync(...));

:x: Ignoring message order

// WRONG - older message overwrites newer
await repo.UpdateAsync(entity);

// CORRECT - check timestamp
if (existing.LastMessageSyncDate <= message.CreatedUtcDate)

Verification Checklist

  • Data ownership clearly defined
  • Message bus pattern used (not direct DB)
  • Dependencies waited for in consumers
  • Race conditions handled with timestamps
  • Soft delete synchronized properly
  • Force sync mechanism available
  • Monitoring/alerting in place