Instruction file imported from ahmetcelik05/AbpGoat (
.cursor/rules/template/app.mdc). Copyright stays with the author.
ABP Layered Application Template
Docs: https://abp.io/docs/latest/solution-templates/layered-web-application
This template follows Domain-Driven Design (DDD) principles with strict layer separation.
Solution Structure
MyProject/
├── src/
│ ├── AbpGoat.Domain.Shared/ # Constants, enums, localization, ETOs
│ ├── AbpGoat.Domain/ # Entities, repository interfaces, domain services
│ ├── AbpGoat.Application.Contracts/ # DTOs, service interfaces
│ ├── AbpGoat.Application/ # Application service implementations
│ ├── AbpGoat.EntityFrameworkCore/ # EF Core DbContext, repository implementations
│ ├── AbpGoat.HttpApi/ # REST API controllers (optional)
│ ├── AbpGoat.HttpApi.Client/ # Client proxies for remote calls
│ ├── AbpGoat.Web/ # MVC/Razor Pages UI
│ └── AbpGoat.DbMigrator/ # Database migration console app
└── test/
├── AbpGoat.Domain.Tests/
├── AbpGoat.Application.Tests/
└── AbpGoat.EntityFrameworkCore.Tests/
Layer Responsibilities
| Layer | Responsibility | References |
|---|---|---|
| Domain.Shared | Constants, enums, localization keys, ETOs | Nothing |
| Domain | Entities, aggregate roots, domain services, repository interfaces | Domain.Shared |
| Application.Contracts | DTOs, service interfaces | Domain.Shared |
| Application | Use case orchestration, mapping | Domain, Application.Contracts |
| EntityFrameworkCore | DbContext, repository implementations, migrations | Domain |
| HttpApi | REST controllers (if not using Auto API) | Application.Contracts |
| Web | MVC/Razor Pages UI components | Application.Contracts |
| DbMigrator | Database migration + seeding console app | EntityFrameworkCore |
Key Differences from Other Templates
| Layered (This) | Single-Layer | Microservice |
|---|---|---|
| Strict DDD layers | Single project | Single project per service |
| DTOs in Application.Contracts | DTOs in Services folder | DTOs in Contracts project |
| Custom repository interfaces in Domain | Generic repositories only | Generic repositories only |
| Multiple module classes | Single module class | Single module class per service |
| DbMigrator project | Migration in host | Migration per service |
Adding a New Feature
1. Entity in Domain
public class Book : AuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public decimal Price { get; private set; }
protected Book() { } // For ORM
public Book(Guid id, string name, decimal price) : base(id)
{
SetName(name);
SetPrice(price);
}
public void SetName(string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name), maxLength: BookConsts.MaxNameLength);
}
public void SetPrice(decimal price)
{
Price = Check.Range(price, nameof(price), 0, 9999);
}
}
2. Constants in Domain.Shared
public static class BookConsts
{
public const int MaxNameLength = 128;
}
3. Repository Interface in Domain (if custom queries needed)
public interface IBookRepository : IRepository<Book, Guid>
{
Task<Book> FindByNameAsync(string name);
}
4. DTOs in Application.Contracts
public class BookDto : EntityDto<Guid>
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class CreateBookDto
{
[Required]
[StringLength(BookConsts.MaxNameLength)]
public string Name { get; set; }
[Range(0, 9999)]
public decimal Price { get; set; }
}
5. Service Interface in Application.Contracts
public interface IBookAppService : IApplicationService
{
Task<BookDto> GetAsync(Guid id);
Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input);
Task<BookDto> CreateAsync(CreateBookDto input);
}
6. Service Implementation in Application
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly BookMapper _bookMapper;
public BookAppService(IRepository<Book, Guid> bookRepository, BookMapper bookMapper)
{
_bookRepository = bookRepository;
_bookMapper = bookMapper;
}
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return _bookMapper.MapToDto(book);
}
[Authorize(MyProjectPermissions.Books.Create)]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = new Book(GuidGenerator.Create(), input.Name, input.Price);
await _bookRepository.InsertAsync(book);
return _bookMapper.MapToDto(book);
}
}
7. DbContext Configuration in EntityFrameworkCore
public DbSet<Book> Books { get; set; }
// In OnModelCreating
builder.Entity<Book>(b =>
{
b.ToTable(MyProjectConsts.DbTablePrefix + "Books", MyProjectConsts.DbSchema);
b.ConfigureByConvention();
b.Property(x => x.Name).IsRequired().HasMaxLength(BookConsts.MaxNameLength);
});
8. Run DbMigrator
cd src/AbpGoat.EntityFrameworkCore
dotnet ef migrations add Added_Book
cd ../AbpGoat.DbMigrator
dotnet run
DbMigrator
Console application for:
- Applying database migrations
- Seeding initial data (admin user, permissions, etc.)
Run before first launch:
dotnet run --project src/AbpGoat.DbMigrator
Best Practices
- Respect layer boundaries - Never referenceEntityFrameworkCorefrom Application
- Rich domain model - Encapsulate business logic in entities
- Repository per aggregate - One repository per aggregate root only
- DTOs at boundaries - Never expose entities outside Application layer
- Use DbMigrator - For consistent database setup across environments
- Auto API Controllers - Let ABP generate controllers from app services
- Mapperly for mapping - Use compile-time mapper for performance