cross-service-integration
1
总安装量
1
周安装量
#47463
全站排名
安装命令
npx skills add https://github.com/congdon1207/agents.md --skill cross-service-integration
Agent 安装分布
opencode
1
codex
1
claude-code
1
Skill 文档
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
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â EasyPlatform Platform â
âââââââââââââââââ¬ââââââââââââââââ¬ââââââââââââââââ¬âââââââââââââââââââââ¤
â TextSnippet â TextSnippet â TextSnippet â TextSnippet â
â (Example) â (Example) â (Example) â (Example) â
âââââââââââââââââ´ââââââââââââââââ´ââââââââââââââââ´âââââââââââââââââââââ¤
â Accounts Service â
â (Authentication & Users) â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
â Shared Infrastructure â
â RabbitMQ â Redis â MongoDB â PostgreSQL â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Communication Patterns
Pattern 1: Entity Event Bus (Recommended)
Use when: Source service owns data, target services need copies.
Source Service Target Service
ââââââââââââââ ââââââââââââââ
â Employee âââââ Create âââââ¶ â Repository â
â Repository â ââââââââââââââ
ââââââââââââââ â
â â
â Auto-raise â
â¼ â¼
ââââââââââââââ ââââââââââââââ
â Producer âââ RabbitMQ âââââ¶ â Consumer â
ââââââââââââââ ââââââââââââââ
Implementation:
// Producer (Source: Accounts)
internal sealed class EmployeeEntityEventBusMessageProducer
: PlatformCqrsEntityEventBusMessageProducer<EmployeeEntityEventBusMessage, Employee, string>
{
public override async Task<bool> HandleWhen(PlatformCqrsEntityEvent<Employee> @event)
=> @event.EntityData.IsActive || @event.CrudAction == PlatformCqrsEntityEventCrudAction.Deleted;
}
// Consumer (Target: TextSnippet)
internal sealed class UpsertEmployeeConsumer
: PlatformApplicationMessageBusConsumer<EmployeeEntityEventBusMessage>
{
public override async Task HandleLogicAsync(EmployeeEntityEventBusMessage 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 TextSnippet, calling Accounts API
public class AccountsApiClient
{
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 | Accounts | All services |
| Employee | TextSnippet | TextSnippet, TextSnippet |
| Candidate | TextSnippet | TextSnippet (on hire) |
| Company | Accounts | All services |
| Survey | TextSnippet | TextSnippet |
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 EmployeeSyncConsumer : PlatformApplicationMessageBusConsumer<EmployeeEventBusMessage>
{
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 PlatformExampleApp.Shared
- 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(employee); // Employee.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