Module Usage
MiCake uses a modular architecture - all features are built around the module system. Modules are the basic building blocks of an application and provide a way to organize code and manage dependencies.
What is a Module?
Section titled “What is a Module?”A module is a code unit with independent functionality that can:
- Configure its own services
- Declare dependencies on other modules
- Execute initialization logic at different stages of the application lifecycle
- Encapsulate specific business or technical functionality
MiCake’s core modules:
MiCakeEssentialModule- the core moduleMiCakeAspNetCoreModule- the ASP.NET Core integration moduleMiCakeEntityFrameworkCoreModule- the EF Core integration module
Creating a Module
Section titled “Creating a Module”A Basic Module
Section titled “A Basic Module”Inherit the MiCakeModule base class:
using MiCake.Core.Modularity;using Microsoft.Extensions.DependencyInjection;
public class MyModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { // Configure services context.Services.AddScoped<IMyService, MyService>();
base.ConfigureServices(context); }}The Entry Module
Section titled “The Entry Module”An application needs an entry module, which usually depends on MiCakeAspNetCoreModule:
using MiCake.AspNetCore.Modules;using MiCake.Core.Modularity;
[RelyOn(typeof(MiCakeAspNetCoreModule))]public class MyAppModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { // Automatically register repositories context.AutoRegisterRepositories(typeof(MyAppModule).Assembly);
// Configure options context.Services.Configure<MyOptions>(options => { options.Setting1 = "Value1"; });
base.ConfigureServices(context); }
public override void Initialization(ModuleInitializationContext context) { // Initialization logic var logger = context.ServiceProvider.GetService<ILogger<MyAppModule>>(); logger?.LogInformation("MyAppModule initialized");
base.Initialization(context); }}Module Lifecycle
Section titled “Module Lifecycle”Modules have three main lifecycle hooks:
1. ConfigureServices - Configuring Services
Section titled “1. ConfigureServices - Configuring Services”Called at application startup to register services into the DI container:
public override void ConfigureServices(ModuleConfigServiceContext context){ var services = context.Services; var configuration = context.Configuration;
// Register services services.AddScoped<IOrderService, OrderService>(); services.AddSingleton<ICacheService, MemoryCacheService>();
// Configure options services.Configure<OrderOptions>(configuration.GetSection("Order"));
// Add an HTTP client services.AddHttpClient("ExternalApi", client => { client.BaseAddress = new Uri("https://api.example.com"); });
base.ConfigureServices(context);}2. Initialization - Initializing
Section titled “2. Initialization - Initializing”Called after application startup to execute initialization logic:
public override void Initialization(ModuleInitializationContext context){ var serviceProvider = context.ServiceProvider;
// Get services and run initialization var dbContext = serviceProvider.GetRequiredService<MyDbContext>();
// Ensure the database has been created dbContext.Database.EnsureCreated();
// Initialize the cache var cacheService = serviceProvider.GetService<ICacheService>(); cacheService?.Initialize();
base.Initialization(context);}3. Shutdown - Shutting Down
Section titled “3. Shutdown - Shutting Down”Called when the application shuts down to clean up resources:
public override void Shutdown(){ // Clean up resources _logger?.LogInformation("MyModule is shutting down");
// Release resources _cache?.Dispose();
base.Shutdown();}Module Dependencies
Section titled “Module Dependencies”Declaring Dependencies
Section titled “Declaring Dependencies”Use the [RelyOn] attribute to declare module dependencies:
// Depend on a single module[RelyOn(typeof(MiCakeAspNetCoreModule))]public class MyModule : MiCakeModule{ // ...}
// Depend on multiple modules[RelyOn(typeof(MiCakeAspNetCoreModule))][RelyOn(typeof(MyOtherModule))]public class MyModule : MiCakeModule{ // ...}
// Or use an array[RelyOn(typeof(MiCakeAspNetCoreModule), typeof(MyOtherModule))]public class MyModule : MiCakeModule{ // ...}Dependency Resolution
Section titled “Dependency Resolution”MiCake resolves module dependencies automatically, ensuring modules are initialized in the correct order:
MiCakeEssentialModule (the core module) ↓MiCakeAspNetCoreModule (depends on the core module) ↓MyAppModule (depends on the AspNetCore module)Initialization order:
1. MiCakeEssentialModule.ConfigureServices2. MiCakeAspNetCoreModule.ConfigureServices3. MyAppModule.ConfigureServices4. MiCakeEssentialModule.Initialization5. MiCakeAspNetCoreModule.Initialization6. MyAppModule.InitializationModule Configuration
Section titled “Module Configuration”Accessing Configuration
Section titled “Accessing Configuration”Access the application configuration in a module:
public class MyModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { var configuration = context.Configuration;
// Read configuration var connectionString = configuration.GetConnectionString("DefaultConnection"); var apiKey = configuration["ExternalApi:ApiKey"];
// Configure services context.Services.AddDbContext<MyDbContext>(options => { options.UseSqlServer(connectionString); });
base.ConfigureServices(context); }}Advanced Lifecycle Methods
Section titled “Advanced Lifecycle Methods”For scenarios that need finer control, modules provide additional lifecycle methods:
PreConfigServices - Before Configuring Services
Section titled “PreConfigServices - Before Configuring Services”public override void PreConfigServices(ModuleConfigServiceContext context){ // Executed before ConfigureServices // Used to configure services that must be registered first
context.Services.AddSingleton<IEarlyService, EarlyService>();
base.PreConfigServices(context);}PostConfigServices - After Configuring Services
Section titled “PostConfigServices - After Configuring Services”public override void PostConfigServices(ModuleConfigServiceContext context){ // Executed after ConfigureServices // Used to validate or adjust the registered services
var services = context.Services;
// Validate that required services are registered if (!services.Any(d => d.ServiceType == typeof(IRequiredService))) { throw new InvalidOperationException("IRequiredService is not registered"); }
base.PostConfigServices(context);}PreInitialization - Before Initialization
Section titled “PreInitialization - Before Initialization”public override void PreInitialization(ModuleInitializationContext context){ // Executed before Initialization // Used to prepare the resources needed for initialization
base.PreInitialization(context);}PostInitialization - After Initialization
Section titled “PostInitialization - After Initialization”public override void PostInitialization(ModuleInitializationContext context){ // Executed after Initialization // Used to validate the initialization result or run follow-up operations
base.PostInitialization(context);}Feature Module Example
Section titled “Feature Module Example”A Data Access Module
Section titled “A Data Access Module”using MiCake.Core.Modularity;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.DependencyInjection;
[RelyOn(typeof(MiCakeEssentialModule))]public class DataAccessModule : MiCakeModule{ public override void ConfigureServices(ModuleConfigServiceContext context) { var configuration = context.Configuration;
// Configure the DbContext context.Services.AddDbContext<AppDbContext>(options => { options.UseSqlServer( configuration.GetConnectionString("DefaultConnection"), sqlOptions => { sqlOptions.EnableRetryOnFailure(5); sqlOptions.CommandTimeout(30); }); });
// Automatically register repositories context.AutoRegisterRepositories(typeof(DataAccessModule).Assembly);
base.ConfigureServices(context); }
public override void Initialization(ModuleInitializationContext context) { // Initialize the database var dbContext = context.ServiceProvider.GetRequiredService<AppDbContext>(); dbContext.Database.Migrate();
base.Initialization(context); }}Module Organization Suggestions
Section titled “Module Organization Suggestions”Organize by Feature
Section titled “Organize by Feature”MyApp.Core (the core module) - Domain/ - Services/ - CoreModule.cs
MyApp.DataAccess (the data access module) - DbContext/ - Repositories/ - DataAccessModule.cs
MyApp.Application (the application module) - UseCases/ - Dtos/ - ApplicationModule.cs
MyApp.Web (the web module) - Controllers/ - WebModule.cs (the entry module)Organize by Layer
Section titled “Organize by Layer”MyApp (the entry module) ↓MyApp.Application (the application layer module) ↓MyApp.Domain (the domain layer module) ↓MyApp.Infrastructure (the infrastructure module)Registering a Module
Section titled “Registering a Module”Register the entry module in Startup.cs:
public void ConfigureServices(IServiceCollection services){ services.AddControllers();
// Register MiCake and the entry module services.AddMiCakeWithDefault<MyAppModule, MyDbContext>(options => { options.AppConfig = app => { // Application configuration }; }).Build();}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ app.UseRouting();
// Start MiCake app.StartMiCake();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });}Module Best Practices
Section titled “Module Best Practices”1. Declare Dependencies Explicitly
Section titled “1. Declare Dependencies Explicitly”Use [RelyOn] to declare dependencies explicitly:
// ✅ Correct: explicit dependency[RelyOn(typeof(DataAccessModule))]public class ApplicationModule : MiCakeModule { }
// ❌ Wrong: implicit dependencypublic class ApplicationModule : MiCakeModule{ // Uses services from DataAccessModule, but does not declare the dependency}2. Avoid Circular Dependencies
Section titled “2. Avoid Circular Dependencies”// ❌ Wrong: circular dependency[RelyOn(typeof(ModuleB))]public class ModuleA : MiCakeModule { }
[RelyOn(typeof(ModuleA))]public class ModuleB : MiCakeModule { }
// ✅ Correct: extract the common dependencypublic class CommonModule : MiCakeModule { }
[RelyOn(typeof(CommonModule))]public class ModuleA : MiCakeModule { }
[RelyOn(typeof(CommonModule))]public class ModuleB : MiCakeModule { }- The entry module must be specified: specify it in
AddMiCakeWithDefault - Lifecycle method order: understand the execution order of the lifecycle methods
- Dependency resolution: MiCake resolves module dependencies automatically and initializes them in the correct order
- Avoid circular dependencies: make sure there are no circular dependencies between modules
- Call the base method: remember to call the
basemethod when overriding lifecycle methods
