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.
More from front-depiction/claude-setup
command-executor
Execute system commands and manage processes using Effect's Command module from @effect/platform. Use this skill when spawning child processes, running shell commands, capturing command output, or managing long-running processes with cleanup.
13spec-driven-development
Implement the complete spec-driven development workflow from instructions through requirements, design, and implementation planning. Use this skill when starting new features or major refactorings that benefit from structured planning before coding.
10react-composition
Build composable React components using Effect Atom for state management. Use this skill when implementing React UIs that avoid boolean props, embrace component composition, and integrate with Effect's reactive state system.
10domain-modeling
Create production-ready Effect domain models using Schema.TaggedStruct for ADTs, Schema.Data for automatic equality, with comprehensive predicates, orders, guards, and match functions. Use when modeling domain entities, value objects, or any discriminated union types.
10effect-ai-streaming
Master Effect AI streaming response patterns including start/delta/end protocol, accumulation strategies, resource-safe consumption, and history management with SubscriptionRef.
9writing-laws
Write formal laws and covenants for codebases using proper legal-style structure. Use when establishing inviolable standards, architectural constraints, or domain-specific rules that must be followed without exception.
9