9.2 KiB
9.2 KiB
Container Feature Integration Guide
Overview
This guide demonstrates how the Container's three major features work together to provide powerful dependency management:
- Contextual Binding - Different implementations based on context
- Method Injection - Automatic dependency resolution for methods
- Tagged Bindings - Grouping related services
Real World Example: Multi-tenant Reporting System
Let's build a complete multi-tenant reporting system that showcases all three features working together.
System Requirements
-
Multiple tenants (clients) each need their own:
- Database connection
- Storage system
- Report formatting
-
Various types of reports:
- Performance reports
- Financial reports
- User activity reports
-
Each report needs:
- Data access
- Formatting
- Storage
- Logging
Base Interfaces
/// Base interface for all reports
abstract class Report {
Future<void> generate();
Future<void> save();
}
/// Database connection interface
abstract class Database {
Future<List<Map<String, dynamic>>> query(String sql);
}
/// Storage system interface
abstract class Storage {
Future<void> save(String path, List<int> data);
Future<List<int>> load(String path);
}
/// Report formatter interface
abstract class ReportFormatter {
String format(Map<String, dynamic> data);
}
Tenant-Specific Implementations
/// Tenant A's database implementation
class TenantADatabase implements Database {
@override
Future<List<Map<String, dynamic>>> query(String sql) {
// Tenant A specific database logic
}
}
/// Tenant B's database implementation
class TenantBDatabase implements Database {
@override
Future<List<Map<String, dynamic>>> query(String sql) {
// Tenant B specific database logic
}
}
/// Similar implementations for Storage and Formatter...
Report Implementations
class PerformanceReport implements Report {
final Database db;
final Storage storage;
final ReportFormatter formatter;
PerformanceReport(this.db, this.storage, this.formatter);
@override
Future<void> generate() async {
var data = await db.query('SELECT * FROM performance_metrics');
var formatted = formatter.format(data);
await storage.save('performance.report', formatted.codeUnits);
}
}
// Similar implementations for Financial and UserActivity reports...
Using All Three Features Together
- First, set up contextual bindings for tenant-specific services:
void configureTenantA(Container container) {
// Bind tenant-specific implementations
container.when(TenantAContext)
.needs<Database>()
.give(TenantADatabase());
container.when(TenantAContext)
.needs<Storage>()
.give(TenantAStorage());
container.when(TenantAContext)
.needs<ReportFormatter>()
.give(TenantAFormatter());
}
void configureTenantB(Container container) {
// Similar bindings for Tenant B...
}
- Set up tagged bindings for reports:
void configureReports(Container container) {
// Bind report implementations
container.bind<PerformanceReport>(PerformanceReport);
container.bind<FinancialReport>(FinancialReport);
container.bind<UserActivityReport>(UserActivityReport);
// Tag them for easy retrieval
container.tag([
PerformanceReport,
FinancialReport,
UserActivityReport
], 'reports');
// Additional tags for categorization
container.tag([PerformanceReport], 'metrics-reports');
container.tag([FinancialReport], 'financial-reports');
}
- Create a report manager that uses method injection:
class ReportManager {
final Container container;
ReportManager(this.container);
/// Generates all reports for a tenant
/// Uses method injection for the logger parameter
Future<void> generateAllReports(
TenantContext tenant,
{required DateTime date}
) async {
// Get all tagged reports
var reports = container.taggedAs<Report>('reports');
// Generate each report using tenant context
for (var report in reports) {
await container.call(
report,
'generate',
parameters: {'date': date},
context: tenant // Uses contextual binding
);
}
}
/// Generates specific report types
/// Uses method injection for dependencies
Future<void> generateMetricsReports(
TenantContext tenant,
Logger logger, // Injected automatically
MetricsService metrics // Injected automatically
) async {
var reports = container.taggedAs<Report>('metrics-reports');
for (var report in reports) {
logger.info('Generating metrics report: ${report.runtimeType}');
await container.call(report, 'generate', context: tenant);
metrics.recordReportGeneration(report);
}
}
}
Using the Integrated System
void main() async {
var container = Container();
// Configure container
configureTenantA(container);
configureTenantB(container);
configureReports(container);
// Create report manager
var manager = ReportManager(container);
// Generate reports for Tenant A
await manager.generateAllReports(
TenantAContext(),
date: DateTime.now()
);
// Generate only metrics reports for Tenant B
await manager.generateMetricsReports(
TenantBContext()
);
}
How the Features Work Together
-
Contextual Binding ensures:
- Each tenant gets their own implementations
- Services are properly scoped
- No cross-tenant data leakage
-
Method Injection provides:
- Automatic dependency resolution
- Clean method signatures
- Flexible parameter handling
-
Tagged Bindings enable:
- Easy service grouping
- Dynamic service discovery
- Flexible categorization
Common Integration Patterns
- Service Location with Context
// Get tenant-specific service
var db = container.make<Database>(context: tenantContext);
// Get all services of a type for a tenant
var reports = container.taggedAs<Report>('reports')
.map((r) => container.make(r, context: tenantContext))
.toList();
- Method Injection with Tags
Future<void> processReports(Logger logger) async {
// Logger is injected, reports are retrieved by tag
var reports = container.taggedAs<Report>('reports');
for (var report in reports) {
logger.info('Processing ${report.runtimeType}');
await container.call(report, 'process');
}
}
- Contextual Services with Tags
Future<void> generateTenantReports(TenantContext tenant) async {
// Get all reports
var reports = container.taggedAs<Report>('reports');
// Process each with tenant context
for (var report in reports) {
await container.call(
report,
'generate',
context: tenant
);
}
}
Best Practices
- Clear Service Organization
// Group related tags
container.tag([Service1, Service2], 'data-services');
container.tag([Service1], 'cacheable-services');
// Group related contexts
container.when(TenantContext)
.needs<Database>()
.give(TenantDatabase());
- Consistent Dependency Resolution
// Prefer method injection for flexible dependencies
Future<void> processReport(
Report report,
Logger logger, // Injected
MetricsService metrics // Injected
) async {
// Implementation
}
// Use contextual binding for tenant-specific services
container.when(TenantContext)
.needs<Storage>()
.give(TenantStorage());
- Documentation
/// Report processor that handles multiple report types
///
/// Uses the following container features:
/// - Tagged bindings for report retrieval ('reports' tag)
/// - Contextual binding for tenant-specific services
/// - Method injection for logging and metrics
class ReportProcessor {
// Implementation
}
Testing Integrated Features
void main() {
group('Integrated Container Features', () {
late Container container;
setUp(() {
container = Container();
// Set up test bindings
configureTenantA(container);
configureReports(container);
});
test('should handle tenant-specific tagged services', () {
var tenantA = TenantAContext();
// Get all reports for tenant
var reports = container.taggedAs<Report>('reports')
.map((r) => container.make(r, context: tenantA))
.toList();
expect(reports, hasLength(3));
expect(reports.every((r) => r.db is TenantADatabase), isTrue);
});
test('should inject dependencies with context', () async {
var processor = ReportProcessor();
var tenantA = TenantAContext();
await container.call(
processor,
'processReports',
context: tenantA
);
// Verify correct services were injected
verify(() => processor.logger is Logger).called(1);
verify(() => processor.db is TenantADatabase).called(1);
});
});
}
Next Steps
- Implement integration tests
- Add performance monitoring
- Add dependency validation
- Create usage documentation
- Add debugging tools
- Create migration guides
Would you like me to create detailed specifications for any of these next steps?