πŸ—οΈFeatures and Modules

🎯 Understanding Modules and Features

Flexbase organizes enterprise applications into Modules and Features - a clear, business-focused approach that maps directly to your requirements and user interface actions.


πŸ“¦ What is a Module?

A Module represents a complete business domain or functional area of your application. Think of it as a self-contained unit that handles all operations related to a specific business concept.

Examples of Modules:

  • πŸ›’ Orders Module - Complete order management

  • πŸ“¦ Products Module - Product catalog and inventory

  • πŸ’³ Payments Module - Payment processing

  • πŸ‘₯ Customers Module - Customer management

  • πŸ“Š Reports Module - Analytics and reporting


⚑ What is a Feature?

A Feature represents a single business action or operation within a module. Every button click, form submission, or user action in your application typically maps to one feature.

Feature Examples:

  • Insert (POST): AddOrder, CreateProduct, RegisterCustomer

  • Query (GET): GetOrders, SearchProducts, GetCustomerDetails

  • Update (PUT): UpdateOrder, ModifyProduct, EditCustomer

  • Delete (DELETE): CancelOrder, RemoveProduct, DeactivateCustomer


πŸ”„ Business Requirements β†’ Technical Mapping

The Mapping Process:

  1. Identify Business Domains β†’ Create Modules

  2. Identify User Actions β†’ Create Features

  3. Define Data Requirements β†’ Create DTOs

  4. Specify Business Rules β†’ Create Domain Logic


πŸ“‹ Real-World Example: E-Commerce Application

Let's walk through a complete example of how business requirements translate to Flexbase modules and features.

Business Requirements:

"We need an e-commerce system where customers can browse products, place orders, and make payments. Administrators should be able to manage products and view order reports."


πŸ›’ Module 1: Orders Module

Business Domain: Order Management

Features in Orders Module:

1. AddOrder Feature (POST)

Business Requirement: "Customer clicks 'Place Order' button"

Screen Action: Customer fills order form and clicks "Place Order"

Technical Implementation:

// Generated Controller Action
[HttpPost]
[Route("AddOrder")]
public async Task<IActionResult> AddOrder([FromBody] AddOrderDto dto)
{
    return await RunService(201, dto, _processOrdersService.AddOrder);
}

// Generated DTO
public partial class AddOrderDto : DtoBridge
{
    [Required]
    public string CustomerId { get; set; }
    
    [Required]
    public List<OrderItemDto> OrderItems { get; set; }
    
    [Range(0.01, double.MaxValue)]
    public decimal TotalAmount { get; set; }
    
    public string? Notes { get; set; }
}

// Generated Command Handler
public partial class AddOrderHandler : IAddOrderHandler
{
    public virtual async Task Execute(AddOrderCommand cmd, IFlexServiceBusContext serviceBusContext)
    {
        _model = _flexHost.GetDomainModel<Order>().AddOrder(cmd);
        _repoFactory.GetRepo().InsertOrUpdate(_model);
        await _repoFactory.GetRepo().SaveAsync();
        await this.Fire(EventCondition, serviceBusContext);
    }
}

// Domain Logic (You Write This)
public partial class Order : DomainModelBridge
{
    public virtual Order AddOrder(AddOrderCommand cmd)
    {
        // Business validation
        if (cmd.Dto.TotalAmount <= 0)
            throw new BusinessException("Order total must be greater than zero");
        
        // Business rules
        if (cmd.Dto.OrderItems.Count == 0)
            throw new BusinessException("Order must contain at least one item");
        
        // Apply business logic
        this.Convert(cmd.Dto);
        this.SetAdded(cmd.Dto.GetGeneratedId());
        this.CalculateTotal();
        this.SetOrderStatus("Pending");
        
        return this;
    }
}

API Endpoint: POST /api/Orders/AddOrder

2. GetOrders Feature (GET)

Business Requirement: "Admin clicks 'View Orders' to see all orders"

Screen Action: Admin navigates to Orders page

Technical Implementation:

// Generated Controller Action
[HttpGet]
[Route("GetOrders")]
public async Task<IActionResult> GetOrders([FromQuery] GetOrdersQuery query)
{
    return await RunService(200, query, _processOrdersService.GetOrders);
}

// Generated Query DTO
public partial class GetOrdersDto : DtoBridge
{
    public Guid Id { get; set; }
    public string CustomerId { get; set; }
    public string CustomerName { get; set; }
    public decimal TotalAmount { get; set; }
    public string Status { get; set; }
    public DateTime OrderDate { get; set; }
    public int ItemCount { get; set; }
}

// Generated Query Handler
public class GetOrders : FlexiQueryEnumerableBridge<GetOrdersDto>
{
    public override IEnumerable<GetOrdersDto> Fetch()
    {
        return _repoFactory.GetRepo()
            .FindAll<Order>()
            .Include(o => o.Customer)
            .SelectTo<GetOrdersDto>()
            .ToList();
    }
}

// Custom Query with Filters (You Can Extend)
public class GetOrdersByStatus : FlexiQueryEnumerableBridge<GetOrdersDto>
{
    public string Status { get; set; }
    
    public override IEnumerable<GetOrdersDto> Fetch()
    {
        return _repoFactory.GetRepo()
            .FindAll<Order>()
            .Where(o => o.Status == Status)
            .Include(o => o.Customer)
            .SelectTo<GetOrdersDto>()
            .ToList();
    }
}

πŸš€ Database Optimization Magic - No Complex SQL Required!

The beauty of Flexbase queries is that you don't write complex SELECT statements! Here's what happens automatically:

1. Automatic Projection to Output Model

// You define what you want in the output DTO
public partial class GetOrdersDto : DtoBridge
{
    public Guid Id { get; set; }           // Only these fields
    public string CustomerName { get; set; } // are fetched from DB
    public decimal TotalAmount { get; set; } // automatically!
    public string Status { get; set; }
    public DateTime OrderDate { get; set; }
    // Note: No complex SELECT statements needed!
}

2. Database-Level Optimization

What Flexbase generates automatically:

-- Instead of: SELECT * FROM Orders (fetches ALL columns)
-- Flexbase generates: SELECT only the fields you need
SELECT 
    o.Id,
    c.Name AS CustomerName,  -- From joined Customer table
    o.TotalAmount,
    o.Status,
    o.OrderDate
FROM Orders o
LEFT JOIN Customers c ON o.CustomerId = c.Id
WHERE o.Status = @Status

3. Performance Benefits

  • βœ… Only Required Fields - Database fetches only what's in your DTO

  • βœ… Automatic Joins - Include() statements become optimized SQL JOINs

  • βœ… No N+1 Queries - Single query with proper joins

  • βœ… Memory Efficient - No unnecessary data loaded into memory

  • βœ… Network Optimized - Less data transferred over the network

4. Complex Projections Made Simple

// You can add calculated fields without complex SQL
public partial class GetOrdersDto : DtoBridge
{
    public Guid Id { get; set; }
    public string CustomerName { get; set; }
    public decimal TotalAmount { get; set; }
    public string Status { get; set; }
    public DateTime OrderDate { get; set; }
    
    // Calculated fields - Flexbase handles the SQL automatically
    public int DaysSinceOrder { get; set; }
    public string StatusDisplay { get; set; }
    public bool IsHighValue { get; set; }
}

// AutoMapper configuration (generated automatically)
CreateMap<Order, GetOrdersDto>()
    .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.Name))
    .ForMember(dest => dest.DaysSinceOrder, opt => opt.MapFrom(src => (DateTime.UtcNow - src.OrderDate).Days))
    .ForMember(dest => dest.StatusDisplay, opt => opt.MapFrom(src => GetStatusDisplay(src.Status)))
    .ForMember(dest => dest.IsHighValue, opt => opt.MapFrom(src => src.TotalAmount > 1000));

5. What You DON'T Have to Write

-- ❌ You DON'T write this complex SQL:
SELECT 
    o.Id,
    c.Name AS CustomerName,
    o.TotalAmount,
    o.Status,
    o.OrderDate,
    CASE 
        WHEN o.Status = 'Pending' THEN 'Awaiting Processing'
        WHEN o.Status = 'Processing' THEN 'Being Processed'
        WHEN o.Status = 'Shipped' THEN 'On the Way'
        ELSE 'Unknown'
    END AS StatusDisplay,
    DATEDIFF(day, o.OrderDate, GETDATE()) AS DaysSinceOrder,
    CASE 
        WHEN o.TotalAmount > 1000 THEN 1 
        ELSE 0 
    END AS IsHighValue
FROM Orders o
LEFT JOIN Customers c ON o.CustomerId = c.Id
WHERE o.Status = @Status
ORDER BY o.OrderDate DESC

6. What You DO Write (Simple and Clean)

// βœ… You write this simple, readable code:
public class GetOrdersByStatus : FlexiQueryEnumerableBridge<GetOrdersDto>
{
    public string Status { get; set; }
    
    public override IEnumerable<GetOrdersDto> Fetch()
    {
        return _repoFactory.GetRepo()
            .FindAll<Order>()
            .Where(o => o.Status == Status)
            .Include(o => o.Customer)
            .SelectTo<GetOrdersDto>()  // Magic happens here!
            .ToList();
    }
}

🎯 Key Benefits:

  1. No SQL Knowledge Required - Write C# LINQ, get optimized SQL

  2. Automatic Optimization - Only fetch what you need

  3. Type Safety - Compile-time validation of all projections

  4. Maintainable - Changes to DTO automatically update SQL

  5. Performance - Database-level optimization without complexity

  6. Readable - Business logic is clear and understandable

πŸ’‘ The Magic of SelectTo()

The SelectTo<GetOrdersDto>() method:

  • βœ… Analyzes your DTO - Sees what fields you need

  • βœ… Generates optimized SQL - Only selects required columns

  • βœ… Handles joins automatically - Based on Include() statements

  • βœ… Applies projections - Maps complex calculations

  • βœ… Optimizes performance - Database-level efficiency

Result: You focus on business logic while Flexbase handles all the complex SQL optimization automatically!

API Endpoint: GET /api/Orders/GetOrders


πŸ“¦ Module 2: Products Module

Business Domain: Product Catalog Management

Features in Products Module:

1. AddProduct Feature (POST)

Business Requirement: "Admin clicks 'Add New Product' button"

Screen Action: Admin fills product form and clicks "Save Product"

Technical Implementation:

// Generated Controller Action
[HttpPost]
[Route("AddProduct")]
public async Task<IActionResult> AddProduct([FromBody] AddProductDto dto)
{
    return await RunService(201, dto, _processProductsService.AddProduct);
}

// Generated DTO
public partial class AddProductDto : DtoBridge
{
    [Required]
    [StringLength(100)]
    public string Name { get; set; }
    
    [Required]
    [StringLength(500)]
    public string Description { get; set; }
    
    [Range(0.01, double.MaxValue)]
    public decimal Price { get; set; }
    
    [Range(0, int.MaxValue)]
    public int StockQuantity { get; set; }
    
    [Required]
    public string Category { get; set; }
    
    public string? ImageUrl { get; set; }
}

// Domain Logic (You Write This)
public partial class Product : DomainModelBridge
{
    public virtual Product AddProduct(AddProductCommand cmd)
    {
        // Business validation
        if (cmd.Dto.Price <= 0)
            throw new BusinessException("Product price must be greater than zero");
        
        if (cmd.Dto.StockQuantity < 0)
            throw new BusinessException("Stock quantity cannot be negative");
        
        // Apply business logic
        this.Convert(cmd.Dto);
        this.SetAdded(cmd.Dto.GetGeneratedId());
        this.SetProductStatus("Active");
        this.GenerateSku();
        
        return this;
    }
}

API Endpoint: POST /api/Products/AddProduct

2. GetProducts Feature (GET)

Business Requirement: "Customer clicks 'Browse Products' to see available products"

Screen Action: Customer navigates to Products page

Technical Implementation:

// Generated Controller Action
[HttpGet]
[Route("GetProducts")]
public async Task<IActionResult> GetProducts([FromQuery] GetProductsQuery query)
{
    return await RunService(200, query, _processProductsService.GetProducts);
}

// Generated Query DTO
public partial class GetProductsDto : DtoBridge
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public int StockQuantity { get; set; }
    public string Category { get; set; }
    public string Status { get; set; }
    public string Sku { get; set; }
    public string? ImageUrl { get; set; }
}

// Generated Query Handler
public class GetProducts : FlexiQueryEnumerableBridge<GetProductsDto>
{
    public override IEnumerable<GetProductsDto> Fetch()
    {
        return _repoFactory.GetRepo()
            .FindAll<Product>()
            .Where(p => p.Status == "Active")
            .SelectTo<GetProductsDto>()
            .ToList();
    }
}

// Custom Search Query (You Can Extend)
public class SearchProducts : FlexiQueryEnumerableBridge<GetProductsDto>
{
    public string SearchTerm { get; set; }
    public string Category { get; set; }
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }
    
    public override IEnumerable<GetProductsDto> Fetch()
    {
        var query = _repoFactory.GetRepo().FindAll<Product>()
            .Where(p => p.Status == "Active");
        
        if (!string.IsNullOrEmpty(SearchTerm))
            query = query.Where(p => p.Name.Contains(SearchTerm) || p.Description.Contains(SearchTerm));
        
        if (!string.IsNullOrEmpty(Category))
            query = query.Where(p => p.Category == Category);
        
        if (MinPrice.HasValue)
            query = query.Where(p => p.Price >= MinPrice.Value);
        
        if (MaxPrice.HasValue)
            query = query.Where(p => p.Price <= MaxPrice.Value);
        
        return query.SelectTo<GetProductsDto>().ToList();
    }
}

API Endpoint: GET /api/Products/GetProducts


🎯 Screen-to-Feature Mapping Examples

Example 1: Customer Order Screen

Screen Element
User Action
Feature
HTTP Method
Endpoint

"Place Order" Button

Click

AddOrder

POST

/api/Orders/AddOrder

"View My Orders" Link

Click

GetCustomerOrders

GET

/api/Orders/GetCustomerOrders

"Order Details" Link

Click

GetOrderDetails

GET

/api/Orders/GetOrderDetails

"Cancel Order" Button

Click

CancelOrder

PUT

/api/Orders/CancelOrder

Example 2: Admin Product Management Screen

Screen Element
User Action
Feature
HTTP Method
Endpoint

"Add Product" Button

Click

AddProduct

POST

/api/Products/AddProduct

"Edit Product" Button

Click

UpdateProduct

PUT

/api/Products/UpdateProduct

"Delete Product" Button

Click

DeleteProduct

DELETE

/api/Products/DeleteProduct

"Search Products" Input

Type + Enter

SearchProducts

GET

/api/Products/SearchProducts

"Filter by Category" Dropdown

Select

GetProductsByCategory

GET

/api/Products/GetProductsByCategory

Example 3: Customer Dashboard Screen

Screen Element
User Action
Feature
HTTP Method
Endpoint

"View Profile" Link

Click

GetCustomerProfile

GET

/api/Customers/GetCustomerProfile

"Edit Profile" Button

Click

UpdateCustomerProfile

PUT

/api/Customers/UpdateCustomerProfile

"Order History" Tab

Click

GetOrderHistory

GET

/api/Orders/GetOrderHistory

"Wishlist" Tab

Click

GetWishlist

GET

/api/Products/GetWishlist


πŸ—οΈ Complete Module Structure

Typical Module Contains:

πŸ“¦ OrdersModule/
β”œβ”€β”€ 🎯 Domain/
β”‚   β”œβ”€β”€ Order.cs (Domain Model)
β”‚   β”œβ”€β”€ AddOrder.cs (Business Logic)
β”‚   └── Order_SeedData.cs (Test Data)
β”œβ”€β”€ 🌐 Controllers/
β”‚   └── OrdersController.cs (REST API)
β”œβ”€β”€ πŸ“ DTOs/
β”‚   β”œβ”€β”€ AddOrderDto.cs (Input)
β”‚   β”œβ”€β”€ GetOrdersDto.cs (Output)
β”‚   └── OrderDto.cs (Domain)
β”œβ”€β”€ πŸ”§ Handlers/
β”‚   β”œβ”€β”€ AddOrderHandler.cs (Command)
β”‚   └── GetOrdersHandler.cs (Query)
β”œβ”€β”€ πŸ“‘ Events/
β”‚   β”œβ”€β”€ OrderCreatedEvent.cs
β”‚   └── OrderUpdatedEvent.cs
└── πŸ§ͺ Tests/
    β”œβ”€β”€ AddOrderTests.cs
    └── GetOrdersTests.cs

🎯 Feature Development Workflow

Step 1: Identify Business Requirement

"Customer needs to be able to place an order"

Step 2: Map to Screen Action

"Customer clicks 'Place Order' button on order form"

Step 3: Define Feature

"AddOrder feature in Orders module"

Step 4: Generate Code

dotnet run --generate-feature Orders AddOrder

Step 5: Add Business Logic

public virtual Order AddOrder(AddOrderCommand cmd)
{
    // Your business rules here
    ValidateOrder(cmd.Dto);
    ApplyBusinessRules(cmd.Dto);
    return this;
}

Step 6: Test and Deploy

  • βœ… Unit tests generated automatically

  • βœ… Integration tests ready

  • βœ… API documentation generated

  • βœ… Ready for deployment


πŸ“Š Module Planning Template

For Each Business Domain, Ask:

  1. What is the main business concept? β†’ Module Name

  2. What actions can users perform? β†’ Features

  3. What data is needed for each action? β†’ DTOs

  4. What business rules apply? β†’ Domain Logic

  5. What events should be published? β†’ Domain Events

Example: Customer Management Module

Question
Answer

Main Business Concept

Customer Management

User Actions

Register, Login, Update Profile, View Profile, Deactivate

Data Requirements

Name, Email, Phone, Address, Preferences

Business Rules

Email validation, Password requirements, Profile completeness

Events

CustomerRegistered, ProfileUpdated, CustomerDeactivated


πŸš€ Best Practices

Module Design:

  • βœ… Single Responsibility - One business domain per module

  • βœ… Clear Boundaries - Minimal coupling between modules

  • βœ… Consistent Naming - Follow business terminology

  • βœ… Complete Coverage - All related features in one module

Feature Design:

  • βœ… One Action Per Feature - Each feature does one thing

  • βœ… Clear Input/Output - Well-defined DTOs

  • βœ… Business Logic in Domain - Keep controllers thin

  • βœ… Proper Validation - Validate at multiple levels

Naming Conventions:

  • Modules: OrdersModule, ProductsModule, CustomersModule

  • Features: AddOrder, GetOrders, UpdateProduct, DeleteCustomer

  • DTOs: AddOrderDto, GetOrdersDto, OrderDto

  • Handlers: AddOrderHandler, GetOrdersHandler


πŸŽ‰ Summary

Flexbase Modules and Features provide a clear, business-focused approach to enterprise application development:

  • Modules = Business domains (Orders, Products, Customers)

  • Features = User actions (AddOrder, GetProducts, UpdateCustomer)

  • Screen Actions = Direct mapping to features

  • Business Requirements = Clear path to technical implementation

The result: A maintainable, scalable application that directly reflects your business needs and user interface requirements.


Start mapping your business requirements to Flexbase modules and features today. Transform your ideas into working enterprise applications in minutes, not months.

Last updated