micro-block-pattern-summary.md

Summary of the micro-block architecture pattern for AI-native development

micro-block-pattern-summary.mdv1.0.09.8 KB
micro-block-pattern-summary.md(markdown)
1# Micro-block Architecture Summary
2
3*Essential patterns for AI developers working on this codebase*
4
5**Reference**: For complete details see [`utaba/main/patterns/micro-block/micro-block-pattern.md`](utaba/main/patterns/micro-block/micro-block-pattern.md)
6
7## Core Concept
8
9Business logic is implemented as **Commands** - self-contained units that declare their contracts and dependencies explicitly. Commands are discovered and instantiated via registries, enabling automatic dependency injection and composition.
10
11## Command Structure Template
12
13```typescript
14// All types in same file
15export interface MyCommandInput { /* explicit input contract */ }
16export interface MyCommandOutput { /* explicit output contract */ }
17export class MyCommandError extends BaseError { /* custom errors */ }
18
19export class MyCommand extends OutputCommand<MyCommandInput, MyCommandOutput, MyCommandError> {
20  // Service dependencies (injected via constructor)
21  private dbService?: IDatabaseService;
22  
23  // Static metadata for discovery
24  static readonly metadata: CommandMetadata = {
25    name: "MyCommand",
26    description: "Brief description of what this command does",
27    category: "folder-name",  // MUST match parent folder exactly
28    inputType: "MyCommandInput",
29    outputType: "MyCommandOutput", 
30    errorType: "MyCommandError",
31    version: "1.0.0",
32    contractVersion: "1.0.0",
33    dependencies: {
34      services: ["IDatabaseService", "ICacheService"],
35      commands: ["category/SomeOtherCommand"], 
36      external: ["youtube-api"]
37    }
38  };
39
40  constructor(
41    input?: MyCommandInput,
42    private logger?: any,
43    private services?: Record<string, any>,
44    private commands?: Record<string, any>
45  ) {
46    super();
47    if (input) {
48      this.setInput(input);
49    }
50    
51    // Services injected via constructor based on metadata
52    this.dbService = services?.['IDatabaseService'];
53    if (!this.dbService) {
54      throw this.raiseError(new MyCommandError('IDatabaseService required', 'MISSING_SERVICE'));
55    }
56  }
57
58  validate(): void { /* input validation - throws errors */ }
59  async execute(): Promise<MyCommandOutput> { /* business logic */ }
60  getMetadata(): CommandMetadata { return MyCommand.metadata; }
61}
62```
63
64## Critical Rules
65
66### 1. Never Direct Instantiation
67```typescript
68// ❌ WRONG
69const command = new CreateUserCommand(input);
70
71// ✅ CORRECT  
72const command = await commandRegistry.get<CreateUserCommand>(
73  CreateUserCommand, input, logger
74);
75```
76
77### 2. Category = Folder Name
78```
79src/commands/user/CreateUserCommand.ts → category: "user"
80src/commands/cache/SetCacheCommand.ts → category: "cache"
81```
82
83### 3. Required Metadata Fields
84```typescript
85static readonly metadata: CommandMetadata = {
86  name: "CommandName",              // Exact class name
87  description: "What it does",      // Brief description
88  category: "folder-name",          // MUST match parent folder
89  inputType: "InputInterface",      // Input contract type
90  outputType: "OutputInterface",    // Output contract type
91  errorType: "ErrorClass",          // Error class type
92  version: "1.0.0",                 // Implementation version
93  contractVersion: "1.0.0",         // API contract version
94  dependencies: {
95    services: ["IServiceName"],
96    commands: ["category/CommandName"],
97    external: ["package-name"]
98  }
99};
100```
101
102### 4. Constructor Pattern
103```typescript
104constructor(
105  input?: InputType,                    // Optional input (use setInput later)
106  private logger?: any,                 // Optional logger injection
107  private services?: Record<string, any>, // Services injected based on metadata
108  private commands?: Record<string, any>  // Commands injected based on metadata
109) {
110  super();                              // Call parent constructor
111  if (input) {
112    this.setInput(input);               // Set input using inherited method
113  }
114  
115  // Validate injected dependencies
116  this.myService = services?.['IMyService'];
117  if (!this.myService) {
118    throw new MyCommandError('IMyService required', 'MISSING_SERVICE');
119  }
120}
121```
122
123### 5. Dependency Injection - Not Resolution
124```typescript
125// ✅ CORRECT - Dependencies injected via constructor
126this.dbService = services?.['IDatabaseService'];
127this.otherCommand = commands?.['category/OtherCommand'];
128
129// ❌ WRONG - Commands should NOT call ServiceRegistry directly
130const serviceRegistry = ServiceRegistry.getInstance();
131this.dbService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
132```
133
134## Presentation Layer Integration
135
136Presentation layers (API routes, UI components) should be **lightweight orchestration layers** that delegate business logic to Services and Commands:
137
138```typescript
139// ✅ Service-First - Route → Service → Commands  
140export async function handleUserRegistration(requestData: any): Promise<any> {
141  const securityService = serviceRegistry.get<ISecurityService>('ISecurityService');
142  const result = await securityService.registerUser(requestData, baseUrl, context);
143  return { success: true, message: result.message, user: result.user };
144}
145
146// ✅ Command-First - Route → Commands directly
147export async function handleWorkflow(requestData: any): Promise<any> {
148  const serviceRegistry = ServiceRegistry.getInstance();
149  const commandRegistry = serviceRegistry.getCommandRegistry();
150  const command = await commandRegistry.get<WorkflowCommand>(WorkflowCommand, requestData);
151  return { success: true, data: await command.execute() };
152}
153```
154
155**Use Service-First for:** Cross-cutting concerns, service-level coordination  
156**Use Command-First for:** Workflows, isolated operations, direct business logic
157
158## Service Portability
159
160**CRITICAL: Services must be portable** - Never depend on project-specific configuration classes. Use service-specific config interfaces:
161
162```typescript
163// Service-specific configuration interface
164export interface MyServiceConfig {
165  connectionString: string;
166  timeout: number;
167  retryAttempts: number;
168}
169
170// Portable service implementation
171export class MyService extends BaseService implements IMyService {
172  constructor(
173    private config: MyServiceConfig,  // Own config interface
174    private logger: ILogger,           // Interface dependency
175    private otherService?: IOtherService
176  ) {
177    super();
178    
179    // Validate required dependencies after super()
180    if (!this.config) {
181      throw new Error('MyServiceConfig is required for MyService');
182    }
183    if (!this.logger) {
184      throw new Error('ILogger is required for MyService');
185    }
186    // otherService is optional, no validation needed
187  }
188  
189  // Implementation uses this.config, never project-specific config
190}
191```
192
193**ServiceRegistry** maps project config → service config interfaces, enabling portability.
194
195
196## Registry Access Patterns
197
198**Services accessing Commands:** Receive CommandRegistry via constructor injection:
199```typescript
200export class MonitorService extends BaseService {
201  constructor(
202    private config: MonitorServiceConfig,
203    private logger: ILogger,
204    private commandRegistry: CommandRegistry  // ✅ Injected by ServiceRegistry
205  ) {
206    super();
207    
208    // Validate required dependencies
209    if (!this.commandRegistry) {
210      throw new Error('CommandRegistry is required for MonitorService');
211    }
212  }
213  
214  private async performAnalysis(): Promise<any> {
215    // ✅ CORRECT: Use injected CommandRegistry
216    const command = await this.commandRegistry.get<AnalysisCommand>(AnalysisCommand, input);
217    return await command.execute();
218  }
219}
220```
221
222**Commands accessing other Commands:** Use dependency injection via constructor:
223```typescript
224static readonly metadata: CommandMetadata = {
225  dependencies: {
226    commands: ['user/CreateUserCommand', 'email/SendEmailCommand']
227  }
228};
229
230constructor(input?: any, logger?: any, services?: any, commands?: any) {
231  // ✅ Commands injected via constructor
232  this.createUserCommand = commands?.['user/CreateUserCommand'];
233  this.sendEmailCommand = commands?.['email/SendEmailCommand'];
234}
235```
236
237### ❌ NEVER DO THIS:
238```typescript
239// WRONG: Commands should not call ServiceRegistry directly
240const serviceRegistry = ServiceRegistry.getInstance();
241this.dbService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
242
243// WRONG: Direct command instantiation
244const command = new SomeCommand();
245```
246
247
248## Key Benefits for AI Development
249
2501. **Granular Focus**: Each command has single responsibility
2512. **Explicit Contracts**: Input/Output types make requirements clear  
2523. **Dependency Clarity**: Metadata declares exactly what's needed
2534. **Composition Over Inheritance**: Build complex workflows from simple parts
2545. **Testability**: Mock dependencies easily via constructor injection
2556. **Discoverability**: Registry patterns enable automatic discovery
256
257## Common Patterns
258
259### Error Handling
260```typescript
261export class MyCommandError extends BaseError {
262  constructor(message: string, code?: string, details?: Record<string, any>) {
263    super(message, code, details);
264  }
265}
266```
267
268### Validation Pattern
269```typescript
270validate(): void {
271  if (!this.input) {
272    const error = new MyCommandError('Command input cannot be null', 'VALIDATION_ERROR');
273    error.setValidationError('input', 'input');
274    throw this.raiseError(error);
275  }
276  
277  if (!this.input.requiredField) {
278    const error = new MyCommandError('Required field missing', 'VALIDATION_ERROR');
279    error.setValidationError('requiredField', 'input.requiredField');
280    throw this.raiseError(error);
281  }
282}
283```
284
285## Architecture Principles
286
287- **Self-Describing**: Metadata makes components discoverable
288- **Loosely Coupled**: Components interact only through contracts
289- **Composable**: Commands can orchestrate other commands via dependency injection
290- **Portable**: Commands work across different systems
291- **Cacheable**: Registry enables lazy loading and reuse
292- **AI-Friendly**: Granular, focused responsibilities
293
294*Source: [micro-block-pattern-summary.md](https://ucm.utaba.ai/browse/utaba/main/patterns/micro-block/micro-block-pattern-summary.md)*

Metadata

Path
utaba/main/patterns/micro-block/micro-block-pattern-summary.md
Namespace
utaba/main/patterns/micro-block
Author
utaba
Category
patterns
Technology
markdown
Contract Version
1.0.0
MIME Type
text/markdown
Published
23-Jul-2025
Last Updated
23-Jul-2025