Key Objectives of the Article
- Understand what the Factory Pattern is and its role in software design.
- Learn why the Factory Pattern is important, focusing on flexibility, maintainability, and decoupling.
- Explore a real-world example of using the Factory Pattern to dynamically switch between Azure SQL Database, SQLite and In-Memory databases in a .NET Core application.
- Review relevant code samples to implement the Factory Pattern for database configuration.
- Summarize the benefits of using the Factory Pattern in real-world applications.
What is the Factory Pattern?
The Factory Pattern is a creational design pattern that provides a way to create objects without specifying their exact class. Instead of instantiating objects directly, the Factory Pattern delegates the responsibility of object creation to a factory class or method.
Key Features:
- Encapsulation of Object Creation: The logic for creating objects is centralized in a factory, reducing duplication.
- Flexibility: Allows switching between different implementations or configurations without modifying the client code.
- Abstraction: The client code depends on an interface or abstract class, not the concrete implementation.
Why Do We Need the Factory Pattern?
The Factory Pattern is particularly useful in scenarios where:
- Dynamic Object Creation: The type of object to be created is determined at runtime based on configuration or conditions.
- Code Reusability: Centralizing object creation logic makes it reusable across multiple parts of the application.
- Decoupling: It decouples the client code from the concrete implementation, adhering to the Dependency Inversion Principle.
- Simplifies Maintenance: Changes to the object creation logic are isolated in the factory, making the code easier to maintain and extend.
Real-Time Example: Switching Between Azure SQL Database and In-Memory Databases
In a .NET Core application, you may want to switch between databases based on need, for example between an Azure SQL database (for production) and an In-Memory database (for testing or development). The Factory Pattern can encapsulate the logic for configuring the database provider, allowing you to switch databases dynamically based on a configuration setting.
Code Implementation
1. Create the DbContextFactory
Class
The factory encapsulates the logic for configuring the database provider. Uses the configuration in appsettings
to set up the correct database provider.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Infrastructure.Data
{
public static class DbContextFactory
{
public static void ConfigureDbContext(IServiceCollection services, IConfiguration configuration)
{
var databaseProvider = configuration.GetValue<string>("DatabaseProvider");
if (databaseProvider == "InMemory")
{
// Configure In-Memory Database
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb").EnableSensitiveDataLogging().LogTo(Console.WriteLine));
}
else if (databaseProvider == "SQLite")
{
// Configure SQLite Database
var connectionString = configuration.GetConnectionString("SQLiteConnection");
services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(connectionString).EnableSensitiveDataLogging().LogTo(Console.WriteLine));
}
else if (databaseProvider == "AzureSQL")
{
// Configure Azure SQL Database
var connectionString = configuration.GetConnectionString("AzureSQLConnection");
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString).EnableSensitiveDataLogging().LogTo(Console.WriteLine));
}
else
{
throw new InvalidOperationException("Invalid database provider specified in configuration.");
}
}
}
}
2. Update Startup.cs to Use the Factory
Modify the Startup.cs file to use the DbContextFactory
for configuring the AppDbContext
. This class reads configuration and passes it to the registration methods.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Clean Architecture API", Version = "v1" });
});
// Register application services
services.AddTransient<IRepository<Entity>, EntityRepository>();
services.AddScoped<IEntityService, EntityService>();
// Use the DbContextFactory to configure the database
DbContextFactory.ConfigureDbContext(services, _configuration);
}
3. Add Configuration in appsettings.json
Add a configuration setting DatabaseProvider
to specify whether to use the in-memory database or Azure SQL Database. This is used by DbContextFactory to setup the right database provider.
"DatabaseProvider": "AzureSQL", // Options: InMemory, SQLite, AzureSQL
"ConnectionStrings": {
"SQLiteConnection": "Data Source=app.db",
"AzureSQLConnection": "Server=tcp:<your-server-name>.database.windows.net,1433;Initial Catalog=<your-database-name>;Persist Security Info=False;User ID=<your-username>;Password=<your-password>;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
}
diagram: Illustration of control flow
4. Initialize the Database with Seed Data
Ensure the database is initialized with seed data during application startup.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
// Initialize the database with seed data
using (var scope = app.ApplicationServices.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
dbContext.Database.EnsureCreated();
// Seed data
if (!dbContext.Entities.Any())
{
dbContext.Entities.AddRange(new[]
{
new Entity { Id = 1, Name = "Sample Entity 1", Description = "This is a sample entity." },
new Entity { Id = 2, Name = "Sample Entity 2", Description = "This is another sample entity." }
});
dbContext.SaveChanges();
}
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Clean Architecture API V1");
c.RoutePrefix = string.Empty; // Set Swagger UI at the app's root
});
}
Summary
The Factory Pattern is a powerful design pattern that simplifies object creation, promotes flexibility, and adheres to key software design principles like decoupling and reusability. In this article, we explored:
- What the Factory Pattern is and why it is important.
- A real-world example of using the Factory Pattern to switch between SQLite, In-Memory and Azure SQL databases.
- Step-by-step implementation with relevant code samples.
By applying the Factory Pattern, you can make your application more flexible, maintainable, and adaptable to changing requirements.
Top comments (0)