DEV Community

Cover image for Why Modular Monolith Architecture is the Key to Effective AI-Assisted Development
ismail Cagdas
ismail Cagdas

Posted on

Why Modular Monolith Architecture is the Key to Effective AI-Assisted Development

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)
Enter fullscreen mode Exit fullscreen mode

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...
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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..."
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

The AI naturally:

  • Inherits from the correct base class
  • Uses the module's permission definitions
  • Follows the repository pattern
  • Uses ABP's GuidGenerator and ObjectMapper

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(+)
Enter fullscreen mode Exit fullscreen mode

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...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Modules provide focused context that AI can fully understand
  2. ABP conventions guide AI to generate consistent, framework-aligned code
  3. Clear boundaries prevent AI from creating unwanted dependencies
  4. 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)