Use when designing or implementing cross-service communication, data synchronization, or service boundary patterns.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ EasyPlatform Platform โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโค
โ TextSnippet โ TextSnippet โ TextSnippet โ TextSnippet โ
โ (Example) โ (Example) โ (Example) โ (Example) โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโค
โ Accounts Service โ
โ (Authentication & Users) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Shared Infrastructure โ
โ RabbitMQ โ Redis โ MongoDB โ PostgreSQL โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
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
}
}
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:
:x: DO NOT USE: Violates service boundaries
// WRONG - Direct cross-service database access
var accountsData = await accountsDbContext.Users.ToListAsync();
| Entity | Owner Service | Consumers |
|---|---|---|
| User | Accounts | All services |
| Employee | TextSnippet | TextSnippet, TextSnippet |
| Candidate | TextSnippet | TextSnippet (on hire) |
| Company | Accounts | All services |
| Survey | TextSnippet | TextSnippet |
// 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);
}
}
}
// 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);
}
}
// 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);
}
# 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"
# Compare source and target counts
# In source service DB
SELECT COUNT(*) FROM Employees WHERE IsActive = 1;
# In target service DB
SELECT COUNT(*) FROM SyncedEmployees;
// Check for waiting dependencies
Logger.LogWarning("Waiting for Company {CompanyId}", companyId);
// Force reprocess
await messageBus.PublishAsync(message.With(m => m.IsForceSync = true));
: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)