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.
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
Purpose: Handle all order-related operations
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:
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:
π 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
2. Database-Level Optimization
What Flexbase generates automatically:
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
5. What You DON'T Have to Write
6. What You DO Write (Simple and Clean)
π― Key Benefits:
No SQL Knowledge Required - Write C# LINQ, get optimized SQL
Automatic Optimization - Only fetch what you need
Type Safety - Compile-time validation of all projections
Maintainable - Changes to DTO automatically update SQL
Performance - Database-level optimization without complexity
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
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.
// 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;
}
}
// 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();
}
}
// 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!
}
-- 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
// 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));
-- β 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
// β 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();
}
}
// 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;
}
}
// 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();
}
}