AI coding assistants have revolutionized how we write code. Tools like GitHub Copilot, Claude, ChatGPT, and Cursor have become indispensable companions for developers worldwide. However, there's a growing challenge that many developers are experiencing: as your codebase grows, AI-generated code quality decreases significantly.
In this article, we'll explore why this happens and how ABP Framework's modular monolith architecture provides an elegant solution to this problem.
The Problem: AI Struggles with Large Codebases
If you've been using AI coding assistants for a while, you've probably noticed a pattern. When you start a new project, AI suggestions are remarkably accurate. The code is clean, follows best practices, and integrates nicely with your existing code. But as your project grows to hundreds of files and thousands of lines of code, something changes.
Context Window Limitations
AI models have a fundamental constraint: context windows. Even the most advanced models can only process a limited amount of code at once. When your project has 500+ files with complex interdependencies, the AI simply cannot see the full picture.
Consider this scenario:
π MyLargeProject/
βββ π src/
β βββ π Domain/ (50+ files)
β βββ π Application/ (80+ files)
β βββ π Infrastructure/ (40+ files)
β βββ π HttpApi/ (60+ files)
β βββ π Web/ (100+ files)
βββ π tests/ (150+ files)
βββ π shared/ (30+ files)
When you ask AI to implement a new feature, it might see only 10-20 of these files in its context. The result? Code that:
- Duplicates existing functionality
- Violates established patterns in your codebase
- Uses inconsistent naming conventions
- Creates unnecessary coupling between components
- Ignores your custom base classes and utilities
The Consistency Problem
In a large monolithic codebase, there are often multiple ways to accomplish the same task. Your team might have established specific patterns over time, but the AI doesn't know about these conventions unless they're explicitly provided in the context.
// Your established pattern (which AI doesn't see)
public class ProductAppService : ApplicationService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<ProductDto> GetAsync(Guid id)
{
var product = await _productRepository.GetAsync(id);
return ObjectMapper.Map<Product, ProductDto>(product);
}
}
// What AI might generate (inconsistent with your patterns)
public class OrderService
{
private readonly DbContext _context;
public OrderService(DbContext context)
{
_context = context;
}
public async Task<OrderDto> Get(Guid id)
{
var order = await _context.Orders.FindAsync(id);
return new OrderDto
{
Id = order.Id,
// Manual mapping...
};
}
}
The AI-generated code works, but it introduces inconsistency, bypasses your repository abstractions, and ignores your mapping conventions.
The Solution: Modular Monolith Architecture
This is where ABP Framework's modular monolith architecture shines. Instead of one massive codebase, you organize your application into isolated modules.
What is a Module in ABP?
An ABP module is a independent unit that encapsulates a specific business capability:
π Acme.ProductManagement/
βββ π src/
β βββ Acme.ProductManagement.Domain/
β βββ Acme.ProductManagement.Domain.Shared/
β βββ Acme.ProductManagement.Application/
β βββ Acme.ProductManagement.Application.Contracts/
β βββ Acme.ProductManagement.EntityFrameworkCore/
β βββ Acme.ProductManagement.HttpApi/
β βββ Acme.ProductManagement.Web/
βββ π test/
βββ Acme.ProductManagement.Application.Tests/
Each module has:
- Clear boundaries: Well-defined interfaces for communication with other modules
- Independent domain: Its own entities, repositories, and domain services
- Isolated application layer: Application services that don't leak into other modules
- Focused scope: Typically 10-30 files per layer, not hundreds
How This Helps AI
When you work with AI on a modular codebase, you can scope the conversation to a single module. Instead of providing context about your entire 500-file project, you provide context about a 50-file module.
"I'm working on the ProductManagement module in an ABP Framework project.
Here's the module structure and key files:
- Product entity (Domain layer)
- IProductRepository interface
- ProductAppService (Application layer)
I need to add a feature to track product price history..."
Now the AI has:
- A complete picture of the relevant code
- Clear patterns to follow
- Defined boundaries it shouldn't cross
- Consistent conventions throughout
Benefits of Module-Scoped AI Development
1. Reduced Context Size = Better Understanding
A typical ABP module contains a focused set of files that easily fit within AI context windows:
| Scope | Files | AI Understanding |
|---|---|---|
| Full Monolith | 500+ | Poor - sees fragments |
| Single Module | 30-50 | Excellent - sees everything |
When AI can see your entire module, it understands:
- How entities relate to each other
- Which base classes and interfaces to use
- Your naming conventions and patterns
- The existing functionality to avoid duplication
2. ABP Conventions Guide the AI
ABP Framework follows strong conventions. When you tell AI you're working with ABP, it can leverage these conventions:
// AI understands ABP patterns and generates consistent code
public class CategoryAppService : ProductManagementAppService, ICategoryAppService
{
private readonly ICategoryRepository _categoryRepository;
public CategoryAppService(ICategoryRepository categoryRepository)
{
_categoryRepository = categoryRepository;
}
[Authorize(ProductManagementPermissions.Categories.Create)]
public async Task<CategoryDto> CreateAsync(CreateCategoryDto input)
{
var category = new Category(GuidGenerator.Create(), input.Name);
await _categoryRepository.InsertAsync(category);
return ObjectMapper.Map<Category, CategoryDto>(category);
}
}
The AI naturally:
- Inherits from the correct base class
- Uses the module's permission definitions
- Follows the repository pattern
- Uses ABP's
GuidGeneratorandObjectMapper
3. Easier Code Review
When AI-generated code is confined to a module, code review becomes straightforward:
git diff --stat
src/Acme.ProductManagement.Domain/Categories/Category.cs | 25 +++
src/Acme.ProductManagement.Application/Categories/CategoryAppService.cs | 45 +++
src/Acme.ProductManagement.HttpApi/Categories/CategoryController.cs | 30 +++
3 files changed, 100 insertions(+)
All changes are in one module. Reviewers familiar with that module can quickly validate the code quality.
4. Sustainable and Maintainable Code
Modular code generated by AI remains maintainable because:
- It follows established patterns within the module
- Dependencies are explicit and limited
- The code can be understood in isolation
- Future AI sessions can easily pick up where you left off
Practical Tips for AI-Assisted ABP Development
Tip 1: Provide Module Context Upfront
Start your AI conversation with module context:
I'm working on an ABP Framework application.
Current module: Acme.Ordering
Module purpose: Handle customer orders and order processing
Key entities:
- Order (AggregateRoot)
- OrderLine (Entity)
- OrderStatus (Enum)
Existing services:
- IOrderRepository
- OrderManager (Domain Service)
- OrderAppService
I want to add a feature to...
Tip 2: Reference ABP Conventions Explicitly
When asking AI to generate code, mention the conventions:
Generate an Application Service for managing Shipments.
Follow ABP conventions:
- Inherit from ApplicationService
- Use IRepository<T, TKey> for data access
- Use ObjectMapper for DTO mapping
- Define permissions in ShipmentPermissions class
- Use [Authorize] attributes for permission checks
In the following versions of ABP, these instructions will be included in your project out of the box. So, AI will write better and maintainable code by following ABP standards and best practices.
Tip 3: Keep Cross-Module Communication Explicit
When your feature spans multiple modules, break it down:
Step 1: Add the event class in Ordering.Domain.Shared
Step 2: Publish the event from OrderAppService
Step 3: (Separate session) Handle the event in Shipping module
This keeps each AI session focused on one module.
Tip 4: Use Module Templates as Examples
Provide AI with examples from your existing module code:
Here's how we implement app services in this module:
[Example of existing ProductAppService]
Now create a similar service for handling Categories.
Tip 5: Validate Within Module Boundaries
After AI generates code, verify it respects module boundaries:
β
Good: Module references only its own layers and shared contracts
β Bad: Direct references to other module's domain or application layers
// β Bad - crossing module boundaries
using Acme.Inventory.Domain; // Direct reference to another module's domain!
// β
Good - using integration events or shared contracts
using Acme.Inventory.Application.Contracts; // Only reference contracts
Real-World Example: Before and After
Before: AI in a Large Monolith
Prompt: "Add a feature to apply discounts to orders"
AI Result:
- Creates discount logic directly in OrderController
- Duplicates validation that exists in another service
- Uses direct database access instead of repositories
- Ignores existing Price calculation utilities
Time spent fixing: 2 hours
After: AI with Module Focus
Prompt:
In the Acme.Ordering module, add discount support.
- Add DiscountInfo value object to Order aggregate
- Add ApplyDiscount method to Order entity
- Update OrderAppService.CreateAsync to accept discount code
- Use existing IDiscountValidator from Ordering.Domain
AI Result:
- Correctly extends Order aggregate
- Follows existing patterns in the module
- Uses the domain service you mentioned
- Generates consistent, reviewable code
Time spent fixing: 15 minutes
Conclusion
The modular monolith architecture isn't just an architectural patternβit's becoming essential for effective AI-assisted development. As AI tools continue to evolve, codebases that are organized into clear, focused modules will get significantly better results from these tools.
ABP Framework's modular architecture, with its strong conventions, clear boundaries, and standardized patterns, creates an ideal environment for AI-assisted development:
- Modules provide focused context that AI can fully understand
- ABP conventions guide AI to generate consistent, framework-aligned code
- Clear boundaries prevent AI from creating unwanted dependencies
- Standardized patterns make AI-generated code predictable and maintainable
If you're building applications with AI assistance (and who isn't these days?), consider how your architecture affects AI code quality. Investing in a modular structure with ABP Framework pays dividends not just in traditional software engineering benefits, but in dramatically improved AI-assisted development workflows.
The future of development is human-AI collaboration. Make sure your architecture is ready for it.
To learn more about ABP Framework, visit it's documentation.
Have you experienced similar challenges with AI in large codebases? Share your experiences and tips in the comments below!
Top comments (0)