Claude-skill-registry abp-infrastructure-patterns
ABP Framework cross-cutting patterns including authorization, background jobs, distributed events, multi-tenancy, and module configuration. Use when: (1) defining permissions, (2) creating background jobs, (3) publishing/handling distributed events, (4) configuring modules.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/abp-infrastructure-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-abp-infrastructure-patterns && rm -rf "$T"
manifest:
skills/data/abp-infrastructure-patterns/SKILL.mdsource content
ABP Infrastructure Patterns
Cross-cutting concerns and infrastructure patterns for ABP Framework.
Authorization & Permissions
Define Permissions
// Domain.Shared/Permissions/ClinicPermissions.cs public static class ClinicPermissions { public const string GroupName = "Clinic"; public static class Patients { public const string Default = GroupName + ".Patients"; public const string Create = Default + ".Create"; public const string Edit = Default + ".Edit"; public const string Delete = Default + ".Delete"; } public static class Appointments { public const string Default = GroupName + ".Appointments"; public const string Create = Default + ".Create"; public const string Edit = Default + ".Edit"; public const string Delete = Default + ".Delete"; public const string ViewAll = Default + ".ViewAll"; // Admins only } }
Register Permissions
// Application.Contracts/Permissions/ClinicPermissionDefinitionProvider.cs public class ClinicPermissionDefinitionProvider : PermissionDefinitionProvider { public override void Define(IPermissionDefinitionContext context) { var clinicGroup = context.AddGroup(ClinicPermissions.GroupName); var patients = clinicGroup.AddPermission( ClinicPermissions.Patients.Default, L("Permission:Patients")); patients.AddChild(ClinicPermissions.Patients.Create, L("Permission:Patients.Create")); patients.AddChild(ClinicPermissions.Patients.Edit, L("Permission:Patients.Edit")); patients.AddChild(ClinicPermissions.Patients.Delete, L("Permission:Patients.Delete")); var appointments = clinicGroup.AddPermission( ClinicPermissions.Appointments.Default, L("Permission:Appointments")); appointments.AddChild(ClinicPermissions.Appointments.Create, L("Permission:Appointments.Create")); appointments.AddChild(ClinicPermissions.Appointments.ViewAll, L("Permission:Appointments.ViewAll")); } private static LocalizableString L(string name) => LocalizableString.Create<ClinicResource>(name); }
Use Permissions
// Declarative [Authorize(ClinicPermissions.Patients.Create)] public async Task<PatientDto> CreateAsync(CreatePatientDto input) { } // Imperative public async Task<AppointmentDto> GetAsync(Guid id) { var appointment = await _appointmentRepository.GetAsync(id); if (appointment.DoctorId != CurrentUser.Id) { await AuthorizationService.CheckAsync(ClinicPermissions.Appointments.ViewAll); } return _mapper.AppointmentToDto(appointment); } // Check without throwing public async Task<bool> CanCreatePatientAsync() => await AuthorizationService.IsGrantedAsync(ClinicPermissions.Patients.Create);
Background Jobs
Define Job
public class AppointmentReminderJob : AsyncBackgroundJob<AppointmentReminderArgs>, ITransientDependency { private readonly IRepository<Appointment, Guid> _appointmentRepository; private readonly IEmailSender _emailSender; public AppointmentReminderJob( IRepository<Appointment, Guid> appointmentRepository, IEmailSender emailSender) { _appointmentRepository = appointmentRepository; _emailSender = emailSender; } public override async Task ExecuteAsync(AppointmentReminderArgs args) { var appointment = await _appointmentRepository.GetAsync(args.AppointmentId); await _emailSender.SendAsync( appointment.Patient.Email, "Appointment Reminder", $"You have an appointment on {appointment.AppointmentDate}"); } } public class AppointmentReminderArgs { public Guid AppointmentId { get; set; } }
Enqueue Job
public async Task<AppointmentDto> CreateAsync(CreateAppointmentDto input) { var appointment = await _appointmentManager.CreateAsync(/*...*/); // Schedule reminder 24 hours before var reminderTime = appointment.AppointmentDate.AddHours(-24); await _backgroundJobManager.EnqueueAsync( new AppointmentReminderArgs { AppointmentId = appointment.Id }, delay: reminderTime - DateTime.Now); return _mapper.AppointmentToDto(appointment); }
Distributed Events
Publish Event
// From entity (recommended for domain events) public class Patient : AggregateRoot<Guid> { public void Activate() { IsActive = true; AddDistributedEvent(new PatientActivatedEto { Id = Id, Name = Name, Email = Email }); } } // From application service public async Task ActivateAsync(Guid id) { var patient = await _patientRepository.GetAsync(id); patient.Activate(); // Or manually publish: await _distributedEventBus.PublishAsync(new PatientActivatedEto { Id = patient.Id, Name = patient.Name, Email = patient.Email }); }
Handle Event
public class PatientActivatedEventHandler : IDistributedEventHandler<PatientActivatedEto>, ITransientDependency { private readonly IEmailSender _emailSender; private readonly ILogger<PatientActivatedEventHandler> _logger; public PatientActivatedEventHandler( IEmailSender emailSender, ILogger<PatientActivatedEventHandler> logger) { _emailSender = emailSender; _logger = logger; } public async Task HandleEventAsync(PatientActivatedEto eventData) { _logger.LogInformation("Patient activated: {Name}", eventData.Name); await _emailSender.SendAsync( eventData.Email, "Welcome", "Your patient account has been activated"); } }
Robust Event Handler (Idempotent + Multi-Tenant)
public class EntitySyncEventHandler : IDistributedEventHandler<EntityUpdatedEto>, ITransientDependency { private readonly IRepository<Entity, Guid> _repository; private readonly IDataFilter _dataFilter; private readonly ILogger<EntitySyncEventHandler> _logger; public async Task HandleEventAsync(EntityUpdatedEto eto) { // Disable tenant filter for cross-tenant sync using (_dataFilter.Disable<IMultiTenant>()) { try { _logger.LogInformation("Processing entity sync: {Id}", eto.Id); // Idempotency check var existing = await _repository.FirstOrDefaultAsync( x => x.ExternalId == eto.ExternalId); if (existing != null) { ObjectMapper.Map(eto, existing); await _repository.UpdateAsync(existing); } else { var entity = ObjectMapper.Map<EntityUpdatedEto, Entity>(eto); await _repository.InsertAsync(entity); } _logger.LogInformation("Entity sync completed: {Id}", eto.Id); } catch (Exception ex) { _logger.LogError(ex, "Entity sync failed: {Id}", eto.Id); throw new UserFriendlyException($"Failed to sync entity: {ex.Message}"); } } } }
Multi-Tenancy
Cross-Tenant Operations
public class CrossTenantService : ApplicationService { private readonly IDataFilter _dataFilter; private readonly ICurrentTenant _currentTenant; public async Task<List<PatientDto>> GetAllTenantsPatients() { using (_dataFilter.Disable<IMultiTenant>()) { return await _patientRepository.GetListAsync(); } } public async Task OperateOnTenant(Guid tenantId) { using (_currentTenant.Change(tenantId)) { await DoTenantSpecificOperation(); } } }
Tenant-Specific Seeding
public async Task SeedAsync(DataSeedContext context) { if (context.TenantId.HasValue) await SeedTenantDataAsync(context.TenantId.Value); else await SeedHostDataAsync(); }
Module Configuration
[DependsOn( typeof(ClinicDomainModule), typeof(AbpIdentityDomainModule), typeof(AbpPermissionManagementDomainModule))] public class ClinicApplicationModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { PreConfigure<AbpIdentityOptions>(options => { options.ExternalLoginProviders.Add<GoogleExternalLoginProvider>(); }); } public override void ConfigureServices(ServiceConfigurationContext context) { Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "Clinic:"; }); context.Services.AddTransient<IPatientAppService, PatientAppService>(); context.Services.AddSingleton<ClinicApplicationMappers>(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); } }
Object Extension
public static class ClinicModuleExtensionConfigurator { public static void Configure() { ObjectExtensionManager.Instance.Modules() .ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty<string>( "Title", property => { property.Attributes.Add(new StringLengthAttribute(64)); }); }); }); } }
Best Practices
- Permissions - Define hierarchically (Parent.Child pattern)
- Background jobs - Use for long-running or delayed tasks
- Distributed events - Use for loose coupling between modules
- Idempotency - Check for existing before insert in event handlers
- Multi-tenancy - Use
sparinglyIDataFilter.Disable<IMultiTenant>() - Module deps - Declare all dependencies explicitly
Related Skills
- Domain layer patternsabp-entity-patterns
- Application layer patternsabp-service-patterns
- OAuth implementationopeniddict-authorization
- Advanced event patternsdistributed-events-advanced