Providers y Servicios
Creative Minds utiliza un sistema de providers y servicios para manejar integraciones externas y lógica de negocio de manera limpia y mantenible.
Providers
Los providers son abstracciones para servicios externos como bases de datos, APIs, sistemas de monitoreo, etc.
Estructura de Providers
src/providers/
├── database/ # Conexión a base de datos
│ └── supabase/ # Cliente de Supabase
├── monitoring/ # Telemetría y logging
├── payments/ # Integración de pagos
└── queries/ # Estado global (TanStack Query)Implementación de Providers
Database Provider
// providers/database/database.provider.ts
import { createClient } from '@supabase/supabase-js'
export class SupabaseProvider {
private static instance: SupabaseClient
private static isInitialized: boolean = false
static async initialize() {
if (this.isInitialized) return
this.instance = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
this.isInitialized = true
}
static async access() {
if (!this.isInitialized) {
await this.initialize()
}
return this.instance
}
}
export const supabaseProvider = SupabaseProviderMonitoring Provider
// providers/monitoring/monitoring.provider.ts
export class MonitoringProvider {
private logger: Logger
constructor() {
this.logger = new Logger('MonitoringProvider')
}
async trackEvent(name: string, properties?: Record<string, any>) {
try {
// Implementación específica de tracking
await analytics.track(name, properties)
} catch (error) {
this.logger.error('Failed to track event', { error })
}
}
async captureError(error: Error) {
try {
// Implementación de captura de errores
await errorReporting.capture(error)
} catch (err) {
this.logger.error('Failed to capture error', { error: err })
}
}
}
export const monitoringProvider = new MonitoringProvider()Servicios
Los servicios encapsulan la lógica de negocio y utilizan los providers para interactuar con servicios externos.
Estructura Base de Servicio
// shared/utils/service.ts
export class Service {
protected logger: Logger
constructor() {
this.logger = new Logger(this.constructor.name)
}
protected async withErrorHandling<T>(
operation: () => Promise<T>,
errorMessage: string
): Promise<T> {
try {
return await operation()
} catch (error) {
this.logger.error(errorMessage, { error })
throw error
}
}
}Implementación de Servicios
Auth Service
// modules/auth/services/auth.service.ts
export class AuthService extends Service {
constructor(
private authAdapter: AuthAdapter,
private monitoringProvider: MonitoringProvider
) {
super()
}
async getCurrentUser() {
return this.withErrorHandling(
async () => {
const session = await this.authAdapter.getSession()
return session?.user
},
'Failed to get current user'
)
}
async login(credentials: LoginCredentials) {
return this.withErrorHandling(
async () => {
const result = await this.authAdapter.signIn(credentials)
await this.monitoringProvider.trackEvent('user_login')
return result
},
'Failed to login user'
)
}
}
export const authService = new AuthService(
authAdapter,
monitoringProvider
)Onboarding Service
// modules/onboarding/services/onboarding.service.ts
export class OnboardingService extends Service {
constructor(
private authService: AuthService,
private database: SupabaseProvider
) {
super()
}
async setUserOnboarding(
onboardingData: CreateOnboardingData
): Promise<{ data: OnboardingResponse | null; error: Error | null }> {
return this.withErrorHandling(
async () => {
const user = await this.authService.getCurrentUser()
const { data, error } = await (await this.database.access())
.from("onboardings")
.insert({
...onboardingData,
user_id: user?.id,
created_at: new Date(),
updated_at: new Date(),
})
return { data, error: error as Error | null }
},
'Failed to set user onboarding'
)
}
}
export const onboardingService = new OnboardingService(
authService,
supabaseProvider
)Integración con Server Actions
Los servicios se utilizan en Server Actions para manejar la lógica de negocio:
// modules/auth/actions/auth.actions.ts
export async function loginAction(
prevState: any,
formData: FormData
) {
const credentials = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
try {
await authService.login(credentials)
redirect('/dashboard')
} catch (error) {
return {
error: 'Invalid credentials'
}
}
}Diagrama de Arquitectura
Mejores Prácticas
-
Singleton Providers: Providers deben ser singleton para mantener una única instancia
-
Error Handling Centralizado: Usar el método
withErrorHandlingen servicios -
Logging Consistente: Utilizar el logger en providers y servicios
-
Tipado Fuerte: Definir interfaces claras para inputs y outputs
-
Inyección de Dependencias: Pasar dependencias en constructores
-
Monitoreo: Integrar tracking y captura de errores
Esta arquitectura de providers y servicios proporciona:
- Separación clara de responsabilidades
- Fácil testeo y mocking
- Código mantenible y escalable
- Gestión eficiente de recursos externos