Rechercher

PythonReactSEOPDF
GitHub Skill

service-implementation

Implement Effect services as fine-grained capabilities avoiding monolithic designs

kriegcloud☆ 44 Étoiles du dépôt5 Forks du dépôt

Installation

Suivez les instructions du dépôt pour votre agent. Consultez les fichiers et dépendances avant l’installation.

Voir la source ↗Guides

Instructions

Service Implementation Skill

Design and implement Effect services as focused capabilities that compose into complete solutions.

Anti-Pattern: Monolithic Services

import { ServiceMap, Effect } from "effect"

// ❌ WRONG - Mixed concerns in one service
export class PaymentService extends ServiceMap.Service<
  PaymentService,
  {
    readonly processPayment: Effect.Effect<void>
    readonly validateWebhook: Effect.Effect<void>
    readonly refund: Effect.Effect<void>
    readonly sendReceipt: Effect.Effect<void>       // Notification concern
    readonly generateReport: Effect.Effect<void>    // Reporting concern
  }
>()("PaymentService") {}

Pattern: Capability-Based Services

Each service represents ONE cohesive capability:

import { ServiceMap, Effect } from "effect"

declare const Doc: unique symbol
type Doc<T extends string> = { readonly [Doc]: T }

interface HandoffResult {
  readonly status: string
}

interface HandoffError {
  readonly _tag: "HandoffError"
  readonly message: string
}

interface WebhookPayload {
  readonly signature: string
  readonly data: unknown
}

interface WebhookValidationError {
  readonly _tag: "WebhookValidationError"
  readonly message: string
}

interface PaymentId {
  readonly value: string
}

interface Cents {
  readonly value: number
}

interface RefundResult {
  readonly status: string
}

interface RefundError {
  readonly _tag: "RefundError"
  readonly message: string
}

// ✅ CORRECT - Focused capabilities

export class PaymentGateway extends ServiceMap.Service<
  PaymentGateway,
  {
    readonly handoff: (
      intent: Doc<"paymentIntents">
    ) => Effect.Effect<HandoffResult, HandoffError, never>
    //                                                 // ▲
    //                                    // No requirements leaked
  }
>()("@services/payment/PaymentGateway") {}

export class PaymentWebhookGateway extends ServiceMap.Service<
  PaymentWebhookGateway,
  {
    readonly validateWebhook: (
      payload: WebhookPayload
    ) => Effect.Effect<void, WebhookValidationError, never>
  }
>()("@services/payment/PaymentWebhookGateway") {}

export class PaymentRefundGateway extends ServiceMap.Service<
  PaymentRefundGateway,
  {
    readonly refund: (
      paymentId: PaymentId,
      amount: Cents
    ) => Effect.Effect<RefundResult, RefundError, never>
  }
>()("@services/payment/PaymentRefundGateway") {}

Pattern: No Requirement Leakage

Service operations should never have requirements:

import { ServiceMap, Effect } from "effect"

interface QueryResult {
  readonly rows: ReadonlyArray<unknown>
}

interface QueryError {
  readonly _tag: "QueryError"
  readonly message: string
}

// The service interface stays clean
export class Database extends ServiceMap.Service<
  Database,
  {
    readonly query: (
      sql: string
    ) => Effect.Effect<QueryResult, QueryError, never>
    //                                             // ▲
    //                                  // Requirements = never
  }
>()("Database") {}

Dependencies are handled during layer construction, not in the service interface:

import { ServiceMap, Effect, Layer } from "effect"

declare const Database: ServiceMap.Service.Tag<
  Database,
  {
    readonly query: (sql: string) => Effect.Effect<QueryResult, QueryError, never>
  }
>

declare const Config: ServiceMap.Service.Tag<
  Config,
  {
    readonly getConfig: Effect.Effect<{ readonly connection: string }>
  }
>

declare const Logger: ServiceMap.Service.Tag<
  Logger,
  {
    readonly log: (message: string) => Effect.Effect<void>
  }
>

interface QueryResult {
  readonly rows: ReadonlyArray<unknown>
}

interface QueryError {
  readonly _tag: "QueryError"
  readonly message: string
}

declare function executeQuery(
  connection: string,
  sql: string
): Effect.Effect<QueryResult, QueryError>

// Dependencies live in the layer
export const DatabaseLive = Layer.effect(
  Database,
  Effect.gen(function* () {
    const config = yield* Config    // Dependency
    const logger = yield* Logger    // Dependency

    return {
      query: 
        Effect.fn(function* (sql) {
          yield* logger.log(`Executing: ${sql}`)
          const { connection } = yield* config.getConfig
          return executeQuery(connection, sql)
        })
    }
  })
)

Pattern: Composing Capabilities

Different implementations support different capabilities:

import { Layer } from "effect"

declare const PaymentGateway: {
  readonly handoff: (intent: any) => any
}

declare const StripeHandoffLive: Layer.Layer<any>
declare const StripeWebhookLive: Layer.Layer<any>
declare const StripeRefundLive: Layer.Layer<any>

declare function fulfillCashPayment(intent: any): any

// Cash payments: Basic handoff only
export const CashGatewayLive = Layer.succeed(
  PaymentGateway,
  {
    handoff: (intent) => fulfillCashPayment(intent)
  }
)

// Stripe: Full capability suite
export const StripeGatewayLive = Layer.mergeAll(
  StripeHandoffLive,      // Implements PaymentGateway
  StripeWebhookLive,      // Implements PaymentWebhookGateway
  StripeRefundLive        // Implements PaymentRefundGateway
)

Pattern: Optional Capabilities

Use Effect.serviceOption for capabilities that may not be available:

import { Effect } from "effect"
import * as O from "effect/Option";
declare const PaymentGateway: {
  handoff: (intent: any) => Effect.Effect<any>
}

declare const PaymentRefundGateway: {
  refund: (paymentId: any, amount: any) => Effect.Effect<any>
}

interface Order {
  readonly paymentIntent: any
  readonly id: string
}

declare function setupRefundPolicy(
  gateway: typeof PaymentRefundGateway,
  order: Order
): Effect.Effect<void>

const processPayment = 
  Effect.fn("processPayment")(function* (order: Order) {
    const handoff = yield* PaymentGateway
    const result = yield* handoff.handoff(order.paymentIntent)

    // Optional capability - check if available
    const refundGateway = yield* Effect.serviceOption(PaymentRefundGateway)

    if (O.isSome(refundGateway)) {
      yield* setupRefundPolicy(refundGateway.value, order)
    }

    return result
  })

Testing Benefits

Each capability can be tested in isolation:

import { Effect, Layer, pipe } from "effect"

declare const PaymentWebhookGateway: ServiceMap.Service.Tag<
  PaymentWebhookGateway,
  {
    validateWebhook: (payload: WebhookPayload) => Effect.Effect<void, WebhookValidationError>
  }
>

interface WebhookPayload {
  readonly signature: string
  readonly data: unknown
}

interface WebhookValidationError {
  readonly _tag: "WebhookValidationError"
  readonly reason: string
}

declare function handleWebhook(payload: WebhookPayload): Effect.Effect<void, WebhookValidationError, any>

declare const payload: WebhookPayload

const TestWebhook = Layer.succeed(
  PaymentWebhookGateway,
  {
    validateWebhook: (payload) =>
      payload.signature === "valid"
        ? Effect.succeed(undefined)
        : Effect.fail(new WebhookValidationError({ reason: "Invalid" }))
  }
)

// Test only webhook validation, no other payment concerns
const testProgram = handleWebhook(payload).pipe(
  Effect.provide(TestWebhook)
)

Naming Convention

Use descriptive capability names:

  • *Gateway - External system integration
  • *Repository - Data persistence
  • *Domain - Business logic
  • *Service - General capability (use sparingly)

Tag identifiers should include namespace:

  • "@services/payment/PaymentGateway"
  • "@repositories/user/UserRepository"
  • "@domain/order/OrderDomain"

Quality Checklist

  • Service represents single capability
  • All operations have Requirements = never
  • Tagged with descriptive namespace
  • Dependencies handled in layer
  • Can be tested in isolation
  • Can be composed with other capabilities
  • JSDoc with purpose and usage

Keep services focused, composable, and free of leaked requirements.

Exemples

Consultez les exemples du dépôt source.

Voir la source ↗

Historique source

Date d’indexation
2026-03-11
Mise à jour de l’index
2026-03-11
Branch
main

Les dates concernent ce répertoire. Consultez le dépôt pour l’historique du code.

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!