
layer-design
by front-depiction
Claude Code Agent Testing Project - Real-time stock trading app built with Effect-TS to test agent configurations and patterns
SKILL.md
name: layer-design description: Design and compose Effect layers for clean dependency management
Layer Design Skill
Create layers that construct services while managing their dependencies cleanly.
Layer Structure
Layer<RequirementsOut, Error, RequirementsIn>
▲ ▲ ▲
│ │ └─ What this layer needs
│ └─ Errors during construction
└─ What this layer produces
Pattern: Simple Layer (No Dependencies)
export class Config extends Context.Tag("Config")<
Config,
{
readonly getConfig: Effect.Effect<ConfigData>
}
>() {}
// Layer<Config, never, never>
// ▲ ▲ ▲
// │ │ └─ No dependencies
// │ └─ Cannot fail
// └─ Produces Config
export const ConfigLive = Layer.succeed(
Config,
Config.of({
getConfig: Effect.succeed({
logLevel: "INFO",
connection: "mysql://localhost/db"
})
})
)
Pattern: Layer with Dependencies
export class Logger extends Context.Tag("Logger")<
Logger,
{ readonly log: (message: string) => Effect.Effect<void> }
>() {}
// Layer<Logger, never, Config>
// ▲ ▲ ▲
// │ │ └─ Needs Config
// │ └─ Cannot fail
// └─ Produces Logger
export const LoggerLive = Layer.effect(
Logger,
Effect.gen(function* () {
const config = yield* Config // Access dependency
return Logger.of({
log: (message) =>
Effect.gen(function* () {
const { logLevel } = yield* config.getConfig
console.log(`[${logLevel}] ${message}`)
})
})
})
)
Pattern: Layer with Resource Management
Use Layer.scoped for resources that need cleanup:
// Layer<Database, DatabaseError, Config>
export const DatabaseLive = Layer.scoped(
Database,
Effect.gen(function* () {
const config = yield* Config
// Acquire resource with automatic release
const connection = yield* Effect.acquireRelease(
connectToDatabase(config),
(conn) => Effect.sync(() => conn.close()) // Cleanup
)
return Database.of({
query: (sql) => executeQuery(connection, sql)
})
})
)
Composing Layers: Merge vs Provide
Merge (Parallel Composition)
Combine independent layers:
// Layer<Config | Logger, never, Config>
// ▲ ▲ ▲
// │ │ └─ LoggerLive needs Config
// │ └─ No errors
// └─ Produces both Config and Logger
const AppConfigLive = Layer.merge(ConfigLive, LoggerLive)
Result combines:
- Requirements: Union (
never | Config = Config) - Outputs: Union (
Config | Logger)
Provide (Sequential Composition)
Chain dependent layers:
// Layer<Logger, never, never>
// ▲ ▲ ▲
// │ │ └─ ConfigLive satisfies LoggerLive's requirement
// │ └─ No errors
// └─ Only Logger in output
const FullLoggerLive = Layer.provide(LoggerLive, ConfigLive)
Result:
- Requirements: Outer layer's requirements (
never) - Output: Inner layer's output (
Logger)
Pattern: Layered Architecture
Build applications in layers:
// Infrastructure: No dependencies
const InfrastructureLive = Layer.mergeAll(
ConfigLive, // Layer<Config, never, never>
DatabaseLive, // Layer<Database, never, Config>
CacheLive // Layer<Cache, never, Config>
).pipe(
Layer.provide(ConfigLive) // Satisfy Config requirement
)
// Domain: Depends on infrastructure
const DomainLive = Layer.mergeAll(
PaymentDomainLive, // Layer<PaymentDomain, never, Database>
OrderDomainLive, // Layer<OrderDomain, never, Database>
).pipe(
Layer.provide(InfrastructureLive)
)
// Application: Depends on domain
const ApplicationLive = Layer.mergeAll(
PaymentGatewayLive,
NotificationServiceLive
).pipe(
Layer.provide(DomainLive)
)
Pattern: Multiple Implementations
Switch implementations for different environments:
// Production
export const DatabaseLive = Layer.scoped(
Database,
Effect.gen(function* () {
const connection = yield* connectToProduction()
return createDatabaseService(connection)
})
)
// Test
export const DatabaseTest = Layer.succeed(
Database,
Database.of({
query: () => Effect.succeed({ rows: [] })
})
)
// Use in application
const program = myProgram.pipe(
Effect.provide(process.env.NODE_ENV === "test" ? DatabaseTest : DatabaseLive)
)
Pattern: Layer Sharing
Layers are memoized - same instance shared across program:
// Config is constructed once and shared
const program = Effect.all([
Effect.gen(function* () {
const config = yield* Config
// Uses shared instance
}),
Effect.gen(function* () {
const config = yield* Config
// Same instance
})
]).pipe(Effect.provide(ConfigLive))
Error Handling in Layers
Handle construction errors:
export const DatabaseLive = Layer.effect(
Database,
Effect.gen(function* () {
const connection = yield* connectToDatabase().pipe(
Effect.catchTag("ConnectionError", (error) =>
Effect.fail(new DatabaseConstructionError({ cause: error }))
)
)
return createDatabaseService(connection)
})
)
Naming Convention
*Live- Production implementation*Test- Test implementation*Mock- Mock for testing- Descriptive names for specialized implementations
Quality Checklist
- Layer type accurately reflects dependencies
- Resource cleanup using
acquireReleaseif needed - Layer can be tested with mock dependencies
- No dependency leakage into service interface
- Appropriate use of merge vs provide
- Error handling for construction failures
- JSDoc with example usage
Layers should make dependency management explicit while keeping service interfaces clean and focused.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon