Claude Code Plugins

Community-maintained marketplace

Feedback

Use when implementing cross-service communication via RabbitMQ message bus.

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 message-bus
description Use when implementing cross-service communication via RabbitMQ message bus.

Message Bus Development

Required Reading

For comprehensive C# backend patterns, you MUST read:

docs/claude/backend-csharp-complete-guide.md - Complete patterns for message bus, cross-service communication, consumers


Message Types

Type Producer Role Naming
Event Leader {Service}{Feature}{Action}EventBusMessage
Request Follower {Consumer}{Feature}RequestBusMessage

Entity Event Producer

Platform automatically handles this - entities sync via PlatformCqrsEntityEventBusMessageProducer.

Consumer Pattern

internal sealed class Upsert{Entity}OnEventConsumer :
    PlatformApplicationMessageBusConsumer<{Entity}EventBusMessage>
{
    private readonly IServiceRepository<{Entity}> repository;

    public override async Task<bool> HandleWhen({Entity}EventBusMessage msg, string routingKey)
        => true;  // Filter logic

    public override async Task HandleLogicAsync({Entity}EventBusMessage msg, string routingKey)
    {
        // Wait for dependencies
        var (companyMissing, userMissing) = await (
            Util.TaskRunner.TryWaitUntilAsync(
                () => companyRepo.AnyAsync(c => c.Id == msg.Payload.EntityData.CompanyId),
                maxWaitSeconds: 300).Then(p => !p),
            Util.TaskRunner.TryWaitUntilAsync(
                () => userRepo.AnyAsync(u => u.Id == msg.Payload.EntityData.UserId),
                maxWaitSeconds: 300).Then(p => !p)
        );

        if (companyMissing || userMissing) return;

        // CREATE/UPDATE
        if (msg.Payload.CrudAction == Created ||
            (msg.Payload.CrudAction == Updated && !msg.Payload.EntityData.IsDeleted))
        {
            var existing = await repository.FirstOrDefaultAsync(
                e => e.Id == msg.Payload.EntityData.Id);

            if (existing == null)
                await repository.CreateAsync(
                    msg.Payload.EntityData.ToEntity()
                        .With(e => e.LastMessageSyncDate = msg.CreatedUtcDate));
            else if (existing.LastMessageSyncDate <= msg.CreatedUtcDate)
                await repository.UpdateAsync(
                    msg.Payload.EntityData.UpdateEntity(existing)
                        .With(e => e.LastMessageSyncDate = msg.CreatedUtcDate));
        }

        // DELETE
        if (msg.Payload.CrudAction == Deleted ||
            (msg.Payload.CrudAction == Updated && msg.Payload.EntityData.IsDeleted))
            await repository.DeleteAsync(msg.Payload.EntityData.Id);
    }
}

Key Patterns

  • TryWaitUntilAsync - Wait for dependencies with timeout
  • LastMessageSyncDate - Prevent race conditions
  • IsForceSyncDataRequest() - Check for force sync requests
  • Consumer class name matches message name + Consumer suffix