Master backend architectures, REST API design, microservices, and server-side development across multiple languages. Use this as a central index to access specialized sub-skills.
日本語の概要は準備中です。原文の説明を表示しています。
ABP Framework application layer patterns including AppServices, DTOs, Mapperly mapping, Unit of Work, and common patterns like Filter DTOs and ResponseModel. Use when: (1) creating AppServices, (2) mapping DTOs with Mapperly, (3) implementing list filtering, (4) wrapping API responses.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Application layer patterns for ABP Framework.
public class PatientAppService : ApplicationService, IPatientAppService
{
private readonly IRepository<Patient, Guid> _patientRepository;
private readonly PatientManager _patientManager; // Domain service
private readonly ClinicApplicationMappers _mapper;
public PatientAppService(
IRepository<Patient, Guid> patientRepository,
PatientManager patientManager,
ClinicApplicationMappers mapper)
{
_patientRepository = patientRepository;
_patientManager = patientManager;
_mapper = mapper;
}
[Authorize(ClinicPermissions.Patients.Default)]
public async Task<PatientDto> GetAsync(Guid id)
{
var patient = await _patientRepository.GetAsync(id);
return _mapper.PatientToDto(patient);
}
[Authorize(ClinicPermissions.Patients.Create)]
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
var patient = await _patientManager.CreateAsync(
input.FirstName, input.LastName, input.Email, input.DateOfBirth);
return _mapper.PatientToDto(patient);
}
[Authorize(ClinicPermissions.Patients.Edit)]
public async Task<PatientDto> UpdateAsync(Guid id, UpdatePatientDto input)
{
var patient = await _patientRepository.GetAsync(id);
_mapper.UpdatePatientFromDto(input, patient);
await _patientRepository.UpdateAsync(patient);
return _mapper.PatientToDto(patient);
}
[Authorize(ClinicPermissions.Patients.Delete)]
public async Task DeleteAsync(Guid id)
{
await _patientRepository.DeleteAsync(id);
}
}
ABP 10.x uses Mapperly (source generator) instead of AutoMapper.
// Application/ClinicApplicationMappers.cs
[Mapper]
public partial class ClinicApplicationMappers
{
// Entity to DTO
public partial PatientDto PatientToDto(Patient patient);
public partial List<PatientDto> PatientsToDtos(List<Patient> patients);
// DTO to Entity (creation)
public partial Patient CreateDtoToPatient(CreatePatientDto dto);
// DTO to Entity (update) - ignores Id
[MapperIgnoreTarget(nameof(Patient.Id))]
public partial void UpdatePatientFromDto(UpdatePatientDto dto, Patient patient);
// Complex mapping with navigation properties
[MapProperty(nameof(Appointment.Patient.FirstName), nameof(AppointmentDto.PatientName))]
[MapProperty(nameof(Appointment.Doctor.FullName), nameof(AppointmentDto.DoctorName))]
public partial AppointmentDto AppointmentToDto(Appointment appointment);
}
Register in Module:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddSingleton<ClinicApplicationMappers>();
}
ABP automatically manages UoW for application service methods.
public class AppointmentAppService : ApplicationService
{
// This method is automatically wrapped in a UoW
// All changes are committed together or rolled back on exception
public async Task<AppointmentDto> CreateAsync(CreateAppointmentDto input)
{
var patient = await _patientRepository.GetAsync(input.PatientId);
patient.LastAppointmentDate = input.AppointmentDate;
var appointment = new Appointment(
GuidGenerator.Create(),
input.PatientId,
input.DoctorId,
input.AppointmentDate);
await _appointmentRepository.InsertAsync(appointment);
// Both changes committed together automatically
return _mapper.AppointmentToDto(appointment);
}
}
Manual UoW Control:
[UnitOfWork(isTransactional: false)] // Disable for read-only
public async Task GenerateLargeReportAsync() { }
public async Task ProcessBatchAsync(List<Guid> ids)
{
foreach (var id in ids)
{
using (var uow = _unitOfWorkManager.Begin(requiresNew: true))
{
await ProcessItemAsync(id);
await uow.CompleteAsync();
}
}
}
Separate query filters from pagination for clean, self-documenting APIs.
Filter DTO:
public class PatientFilter
{
public Guid? DoctorId { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public bool? IsActive { get; set; }
public DateTime? CreatedAfter { get; set; }
public DateTime? CreatedBefore { get; set; }
}
AppService with WhereIf:
public async Task<PagedResultDto<PatientDto>> GetListAsync(
PagedAndSortedResultRequestDto input,
PatientFilter filter)
{
// Trim string inputs
filter.Name = filter.Name?.Trim();
filter.Email = filter.Email?.Trim();
// Default sorting
if (input.Sorting.IsNullOrWhiteSpace())
input.Sorting = nameof(PatientDto.FirstName);
var queryable = await _patientRepository.GetQueryableAsync();
var query = queryable
.WhereIf(filter.DoctorId.HasValue, x => x.DoctorId == filter.DoctorId)
.WhereIf(!filter.Name.IsNullOrWhiteSpace(),
x => x.FirstName.Contains(filter.Name) || x.LastName.Contains(filter.Name))
.WhereIf(!filter.Email.IsNullOrWhiteSpace(),
x => x.Email.ToLower().Contains(filter.Email.ToLower()))
.WhereIf(filter.IsActive.HasValue, x => x.IsActive == filter.IsActive)
.WhereIf(filter.CreatedAfter.HasValue, x => x.CreationTime >= filter.CreatedAfter)
.WhereIf(filter.CreatedBefore.HasValue, x => x.CreationTime <= filter.CreatedBefore);
var totalCount = await AsyncExecuter.CountAsync(query);
var patients = await AsyncExecuter.ToListAsync(
query.OrderBy(input.Sorting).PageBy(input.SkipCount, input.MaxResultCount));
return new PagedResultDto<PatientDto>(totalCount, _mapper.PatientsToDtos(patients));
}
public class ResponseModel<T>
{
public bool IsSuccess { get; set; }
public T Data { get; set; }
public string Message { get; set; }
public static ResponseModel<T> Success(T data, string message = null)
=> new() { IsSuccess = true, Data = data, Message = message };
public static ResponseModel<T> Failure(string message)
=> new() { IsSuccess = false, Message = message };
}
// Usage
public async Task<ResponseModel<PatientDto>> GetAsync(Guid id)
{
var patient = await _patientRepository.FirstOrDefaultAsync(x => x.Id == id);
if (patient == null)
return ResponseModel<PatientDto>.Failure("Patient not found");
return ResponseModel<PatientDto>.Success(_mapper.PatientToDto(patient));
}
Reduce constructor bloat by grouping cross-cutting dependencies.
public class CommonDependencies<T>
{
public IDistributedEventBus DistributedEventBus { get; set; }
public IDataFilter DataFilter { get; set; }
public ILogger<T> Logger { get; set; }
public IGuidGenerator GuidGenerator { get; set; }
}
// Register
context.Services.AddTransient(typeof(CommonDependencies<>));
// Usage
public class PatientAppService : ApplicationService
{
private readonly IRepository<Patient, Guid> _patientRepository;
private readonly CommonDependencies<PatientAppService> _common;
public PatientAppService(
IRepository<Patient, Guid> patientRepository,
CommonDependencies<PatientAppService> common)
{
_patientRepository = patientRepository;
_common = common;
}
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
_common.Logger.LogInformation("Creating patient: {Name}", input.FirstName);
var patient = new Patient(_common.GuidGenerator.Create(), /*...*/);
await _patientRepository.InsertAsync(patient);
await _common.DistributedEventBus.PublishAsync(new PatientCreatedEto { Id = patient.Id });
return _mapper.PatientToDto(patient);
}
}
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
_logger.LogInformation(
"[{Service}] {Method} - Started - Input: {@Input}",
nameof(PatientAppService), nameof(CreateAsync), input);
try
{
var patient = await _patientManager.CreateAsync(/*...*/);
_logger.LogInformation(
"[{Service}] {Method} - Completed - PatientId: {PatientId}",
nameof(PatientAppService), nameof(CreateAsync), patient.Id);
return _mapper.PatientToDto(patient);
}
catch (Exception ex)
{
_logger.LogError(ex,
"[{Service}] {Method} - Failed - Error: {Message}",
nameof(PatientAppService), nameof(CreateAsync), ex.Message);
throw;
}
}
public static class InputSanitization
{
public static string TrimAndLower(this string value) => value?.Trim()?.ToLowerInvariant();
public static string TrimAndUpper(this string value) => value?.Trim()?.ToUpperInvariant();
}
// Usage
public async Task<PatientDto> CreateAsync(CreatePatientDto input)
{
input.Email = input.Email.TrimAndLower();
input.FirstName = input.FirstName?.Trim();
// ...
}
Manual mappings (especially in select new clauses) are prone to copy-paste errors:
// ❌ BUG: Wrong property copied - IsPutawayCompleted mapped from wrong source!
select new LicensePlateDto()
{
IsInboundQCChecklistCompleted = lc.IsInboundQCChecklistCompleted,
IsPutawayCompleted = lc.IsInboundQCChecklistCompleted, // BUG! Should be lc.IsPutawayCompleted
IsHold = lc.IsHold
}
// ✅ CORRECT: Use Mapperly to prevent copy-paste errors
[Mapper]
public partial class LicensePlateMapper
{
public partial LicensePlateDto ToDto(LicensePlate entity);
}
// Or if manual mapping is required, double-check similar-named properties
select new LicensePlateDto()
{
IsInboundQCChecklistCompleted = lc.IsInboundQCChecklistCompleted,
IsPutawayCompleted = lc.IsPutawayCompleted, // ✅ Correct property
IsHold = lc.IsHold
}
When manual mapping is unavoidable (e.g., complex projections), verify:
IsXxxCompleted vs IsYyyCompleted)Be extra careful with these patterns that look similar:
| DTO Property | Wrong Source | Correct Source |
|---|---|---|
IsPutawayCompleted | entity.IsInboundCompleted | entity.IsPutawayCompleted |
UpdatedAt | entity.CreatedAt | entity.LastModificationTime |
CustomerName | entity.ShipperName | entity.CustomerName |
TargetDate | entity.SourceDate | entity.TargetDate |
abp-entity-patterns - Domain layer patternsabp-infrastructure-patterns - Cross-cutting concernsfluentvalidation-patterns - Input validationまだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Master backend architectures, REST API design, microservices, and server-side development across multiple languages. Use this as a central index to access specialized sub-skills.
日本語の概要は準備中です。原文の説明を表示しています。
在每次回复中使用固定前缀并列出本次实际参考的指令/文件,便于校验是否遵循上下文与规则。
日本語の概要は準備中です。原文の説明を表示しています。
Automate 21risk tasks via Rube MCP (Composio). Always search tools first for current schemas.
日本語の概要は準備中です。原文の説明を表示しています。
Automate 2chat tasks via Rube MCP (Composio). Always search tools first for current schemas.
日本語の概要は準備中です。原文の説明を表示しています。
Arquitecto de Soluciones Principal y Consultor Tecnológico de Andru.ia. Diagnostica y traza la hoja de ruta óptima para proyectos de IA en español.
日本語の概要は準備中です。原文の説明を表示しています。
[00] META. Orchestrate skills into dynamic chains for complex tasks. Analyzes the task, discovers available skills, builds an optimal chain, explains why each skill is needed, and executes step-by-step with user confirmation. Use for any complex task requiring multiple thinking/research/analysis steps. Triggers on "plan this", "how to approach", "what's the strategy", "build a plan", or any multi-step problem requiring skill orchestration.
日本語の概要は準備中です。原文の説明を表示しています。