#Providers
If Primitives are the "superpowers" you attach to classes, Providers are the engines that power them.
In strict architectural terms, a Service contains your Business Logic (the "What"), and a Provider contains the Infrastructure Logic (the "How").
#The Separation of Concerns
When writing an application, you often need to talk to the outside world: sending emails, connecting to Redis, uploading files to S3.
You could put this logic directly inside your User Service, but that makes testing a nightmare. Instead, Alepha encourages you to wrap this infrastructure code into a Provider.
At the end, providers are just a convention to make mocking and swapping implementations easier.
#Anatomy of a Provider
A Provider is just a class. However, it typically does three things:
- Validates its own configuration via
$env. - Manages its connection lifecycle via
$hook(connect/disconnect). - Exposes a clean API to the rest of the app.
Here is a production-ready Email Provider:
1import { $env, $hook, t } from "alepha"; 2import { $logger } from "alepha/logger"; 3import { createTransport, type Transporter } from "my-super-mailer-lib"; 4 5export class EmailProvider { 6 log = $logger(); 7 8 // 1. Configuration: The provider validates its own requirements 9 env = $env(t.object({10 SMTP_HOST: t.text(),11 SMTP_USER: t.text(),12 SMTP_PASS: t.text(),13 }));14 15 transporter = createTransport({16 host: this.env.SMTP_HOST,17 auth: { user: this.env.SMTP_USER, pass: this.env.SMTP_PASS }18 });19 20 // 2. Lifecycle: Connect when the app starts21 onStart = $hook({22 on: "start",23 handler: async () => {24 await this.transporter.verify();25 this.log.info("Connected to SMTP");26 }27 });28 29 // 3. Public API: The rest of your app uses this30 async send(to: string, subject: string) {31 return this.transporter.sendMail({ from: "[email protected]", to, subject });32 }33}
#Using a Provider
Injecting a provider is no different than injecting a service. Use $inject.
1import { $inject, $action } from "alepha"; 2import { EmailProvider } from "./EmailProvider"; 3 4class UserService { 5 // Alepha injects the singleton instance of EmailProvider 6 email = $inject(EmailProvider); 7 8 register = $action({ 9 handler: async ({ body }) => {10 // ... logic to create user ...11 await this.email.send(body.email, "Welcome!");12 }13 });14}
#Polymorphism (The "Swap" Strategy)
This is where Alepha shines.
Sometimes, you want the "What" to stay the same, but the "How" to change depending on where the app is running.
- Local Dev: You want to log emails to the console (free, fast).
- Production: You want to send real emails via SendGrid/AWS SES.
You can achieve this using Service Substitution.
#1. Define the Abstract Provider
This defines the "Contract". Your services will depend on this.
1export abstract class QueueProvider {2 abstract push(job: object): Promise<void>;3}
#2. Define Implementations
Different ways to fulfill the contract.
1// For Production: Real Redis 2export class RedisQueueProvider extends QueueProvider { 3 async push(job: object) { /* ... redis logic ... */ } 4} 5 6// For Dev/Test: Just an array in memory 7export class MemoryQueueProvider extends QueueProvider { 8 queue: object[] = []; 9 async push(job: object) { this.queue.push(job); }10}
#3. Wire it up
In your entry point, you tell Alepha which implementation to use based on the environment.
1import { Alepha, run } from "alepha"; 2import { QueueProvider, RedisQueueProvider, MemoryQueueProvider } from "./queue"; 3 4const alepha = Alepha.create(); 5 6// The Magic: Dependency Injection wiring 7if (alepha.isProduction()) { 8 // In Prod: When someone asks for 'QueueProvider', give them 'RedisQueueProvider' 9 alepha.with({ provide: QueueProvider, use: RedisQueueProvider });10} else {11 // In Dev: When someone asks for 'QueueProvider', give them 'MemoryQueueProvider'12 alepha.with({ provide: QueueProvider, use: MemoryQueueProvider });13}14 15// Your app logic doesn't care. It just injects 'QueueProvider'.16run(alepha);
Voilà, now you are an Alepha EXPERT.
#Built-in Providers
Alepha provides standard implementations for common needs so you don't have to reinvent the wheel.
alepha/bucket: File storage. Switches betweenLocalFileStorageProvider(disk) andS3/Azureautomatically.alepha/queue: Job queues. Switches betweenMemoryQueueandRedisQueue.alepha/logger: Logging. Switches betweenConsole(pretty colors) andJSON(for log aggregators).
You focus on the logic; Alepha handles the plumbing.