service-implementation

Implement Effect services as fine-grained capabilities avoiding monolithic designs

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "service-implementation" with this command: npx skills add front-depiction/claude-setup/front-depiction-claude-setup-service-implementation

Service Implementation Skill

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

Anti-Pattern: Monolithic Services

import { Context, Effect } from "effect"

// ❌ WRONG - Mixed concerns in one service
export class PaymentService extends Context.Tag("PaymentService")<
  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
  }
>() {}

Pattern: Capability-Based Services

Each service represents ONE cohesive capability:

import { Context, 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 Context.Tag(
  "@services/payment/PaymentGateway"
)<
  PaymentGateway,
  {
    readonly handoff: (
      intent: Doc<"paymentIntents">
    ) => Effect.Effect<HandoffResult, HandoffError, never>
    //                                                 // ▲
    //                                    // No requirements leaked
  }
>() {}

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

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

Pattern: No Requirement Leakage

Service operations should never have requirements:

import { Context, 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 Context.Tag("Database")<
  Database,
  {
    readonly query: (
      sql: string
    ) => Effect.Effect<QueryResult, QueryError, never>
    //                                             // ▲
    //                                  // Requirements = never
  }
>() {}

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

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

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

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

declare const Logger: Context.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 Database.of({
      query: (sql) =>
        Effect.gen(function* () {
          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: {
  of: (impl: { handoff: (intent: any) => 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,
  PaymentGateway.of({
    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, Option } from "effect"

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 = (order: Order) =>
  Effect.gen(function* () {
    const handoff = yield* PaymentGateway
    const result = yield* handoff.handoff(order.paymentIntent)

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

    if (Option.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: {
  of: (impl: {
    validateWebhook: (payload: WebhookPayload) => Effect.Effect<void, WebhookValidationError>
  }) => any
}

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,
  PaymentWebhookGateway.of({
    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.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

command-executor

No summary provided by upstream source.

Repository SourceNeeds Review
General

react-composition

No summary provided by upstream source.

Repository SourceNeeds Review
General

effect-ai-streaming

No summary provided by upstream source.

Repository SourceNeeds Review
General

wide-events

No summary provided by upstream source.

Repository SourceNeeds Review