effect-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Effect-TS Best Practices

Effect-TS 最佳实践

This skill enforces opinionated, consistent patterns for Effect-TS codebases. These patterns optimize for type safety, testability, observability, and maintainability.
本规则针对Effect-TS代码库强制执行具有主见性、一致性的模式。这些模式在类型安全性、可测试性、可观测性和可维护性方面进行了优化。

Quick Reference: Critical Rules

快速参考:关键规则

CategoryDODON'T
Services
Effect.Service
with
accessors: true
Context.Tag
for business logic
Dependencies
dependencies: [Dep.Default]
in service
Manual
Layer.provide
at usage sites
Errors
Schema.TaggedError
with
message
field
Plain classes or generic Error
Error Specificity
UserNotFoundError
,
SessionExpiredError
Generic
NotFoundError
,
BadRequestError
Error Handling
catchTag
/
catchTags
catchAll
or
mapError
IDs
Schema.UUID.pipe(Schema.brand("@App/EntityId"))
Plain
string
for entity IDs
Functions
Effect.fn("Service.method")
Anonymous generators
Logging
Effect.log
with structured data
console.log
Config
Config.*
with validation
process.env
directly
Options
Option.match
with both cases
Option.getOrThrow
Nullability
Option<T>
in domain types
null
/
undefined
Atoms
Atom.make
outside components
Creating atoms inside render
Atom State
Atom.keepAlive
for global state
Forgetting keepAlive for persistent state
Atom Updates
useAtomSet
in React components
Atom.update
imperatively from React
Atom Cleanup
get.addFinalizer()
for side effects
Missing cleanup for event listeners
Atom Results
Result.builder
with
onErrorTag
Ignoring loading/error states
类别推荐做法禁止做法
服务使用带
accessors: true
Effect.Service
对业务逻辑使用
Context.Tag
依赖项在服务中使用
dependencies: [Dep.Default]
在使用位置手动调用
Layer.provide
错误使用带
message
字段的
Schema.TaggedError
使用普通类或通用Error
错误特异性使用
UserNotFoundError
SessionExpiredError
使用通用的
NotFoundError
BadRequestError
错误处理使用
catchTag
/
catchTags
使用
catchAll
mapError
ID使用
Schema.UUID.pipe(Schema.brand("@App/EntityId"))
对实体ID使用普通
string
函数使用
Effect.fn("Service.method")
使用匿名生成器
日志使用带结构化数据的
Effect.log
使用
console.log
配置使用带验证的
Config.*
直接使用
process.env
可选值使用
Option.match
处理两种情况
使用
Option.getOrThrow
空值处理在领域类型中使用
Option<T>
使用
null
/
undefined
原子在组件外部使用
Atom.make
在渲染内部创建原子
原子状态对全局状态使用
Atom.keepAlive
忘记为持久化状态添加keepAlive
原子更新在React组件中使用
useAtomSet
从React中命令式调用
Atom.update
原子清理对副作用使用
get.addFinalizer()
遗漏事件监听器的清理
原子结果使用带
onErrorTag
Result.builder
忽略加载/错误状态

Service Definition Pattern

服务定义模式

Always use
Effect.Service
for business logic services. This provides automatic accessors, built-in
Default
layer, and proper dependency declaration.
typescript
import { Effect } from "effect"

export class UserService extends Effect.Service<UserService>()("UserService", {
    accessors: true,
    dependencies: [UserRepo.Default, CacheService.Default],
    effect: Effect.gen(function* () {
        const repo = yield* UserRepo
        const cache = yield* CacheService

        const findById = Effect.fn("UserService.findById")(function* (id: UserId) {
            const cached = yield* cache.get(id)
            if (Option.isSome(cached)) return cached.value

            const user = yield* repo.findById(id)
            yield* cache.set(id, user)
            return user
        })

        const create = Effect.fn("UserService.create")(function* (data: CreateUserInput) {
            const user = yield* repo.create(data)
            yield* Effect.log("User created", { userId: user.id })
            return user
        })

        return { findById, create }
    }),
}) {}

// Usage - dependencies are already wired
const program = Effect.gen(function* () {
    const user = yield* UserService.findById(userId)
    return user
})

// At app root
const MainLive = Layer.mergeAll(UserService.Default, OtherService.Default)
When
Context.Tag
is acceptable:
  • Infrastructure with runtime injection (Cloudflare KV, worker bindings)
  • Factory patterns where resources are provided externally
See
references/service-patterns.md
for detailed patterns.
始终为业务逻辑服务使用
Effect.Service
。它会提供自动访问器、内置的
Default
层以及正确的依赖声明。
typescript
import { Effect } from "effect"

export class UserService extends Effect.Service<UserService>()("UserService", {
    accessors: true,
    dependencies: [UserRepo.Default, CacheService.Default],
    effect: Effect.gen(function* () {
        const repo = yield* UserRepo
        const cache = yield* CacheService

        const findById = Effect.fn("UserService.findById")(function* (id: UserId) {
            const cached = yield* cache.get(id)
            if (Option.isSome(cached)) return cached.value

            const user = yield* repo.findById(id)
            yield* cache.set(id, user)
            return user
        })

        const create = Effect.fn("UserService.create")(function* (data: CreateUserInput) {
            const user = yield* repo.create(data)
            yield* Effect.log("User created", { userId: user.id })
            return user
        })

        return { findById, create }
    }),
}) {}

// Usage - dependencies are already wired
const program = Effect.gen(function* () {
    const user = yield* UserService.findById(userId)
    return user
})

// At app root
const MainLive = Layer.mergeAll(UserService.Default, OtherService.Default)
可接受使用
Context.Tag
的场景:
  • 需运行时注入的基础设施(Cloudflare KV、worker绑定)
  • 资源由外部提供的工厂模式
详细模式请参考
references/service-patterns.md

Error Definition Pattern

错误定义模式

Always use
Schema.TaggedError
for errors. This makes them serializable (required for RPC) and provides consistent structure.
typescript
import { Schema } from "effect"
import { HttpApiSchema } from "@effect/platform"

export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
    "UserNotFoundError",
    {
        userId: UserId,
        message: Schema.String,
    },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class UserCreateError extends Schema.TaggedError<UserCreateError>()(
    "UserCreateError",
    {
        message: Schema.String,
        cause: Schema.optional(Schema.String),
    },
    HttpApiSchema.annotations({ status: 400 }),
) {}
Error handling - use
catchTag
/
catchTags
:
typescript
// CORRECT - preserves type information
yield* repo.findById(id).pipe(
    Effect.catchTag("DatabaseError", (err) =>
        Effect.fail(new UserNotFoundError({ userId: id, message: "Lookup failed" }))
    ),
    Effect.catchTag("ConnectionError", (err) =>
        Effect.fail(new ServiceUnavailableError({ message: "Database unreachable" }))
    ),
)

// CORRECT - multiple tags at once
yield* effect.pipe(
    Effect.catchTags({
        DatabaseError: (err) => Effect.fail(new UserNotFoundError({ userId: id, message: err.message })),
        ValidationError: (err) => Effect.fail(new InvalidEmailError({ email: input.email, message: err.message })),
    }),
)
始终为错误使用
Schema.TaggedError
。这会让错误可序列化(RPC场景必需)并提供一致的结构。
typescript
import { Schema } from "effect"
import { HttpApiSchema } from "@effect/platform"

export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
    "UserNotFoundError",
    {
        userId: UserId,
        message: Schema.String,
    },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class UserCreateError extends Schema.TaggedError<UserCreateError>()(
    "UserCreateError",
    {
        message: Schema.String,
        cause: Schema.optional(Schema.String),
    },
    HttpApiSchema.annotations({ status: 400 }),
) {}
错误处理 - 使用
catchTag
/
catchTags
typescript
// CORRECT - preserves type information
yield* repo.findById(id).pipe(
    Effect.catchTag("DatabaseError", (err) =>
        Effect.fail(new UserNotFoundError({ userId: id, message: "Lookup failed" }))
    ),
    Effect.catchTag("ConnectionError", (err) =>
        Effect.fail(new ServiceUnavailableError({ message: "Database unreachable" }))
    ),
)

// CORRECT - multiple tags at once
yield* effect.pipe(
    Effect.catchTags({
        DatabaseError: (err) => Effect.fail(new UserNotFoundError({ userId: id, message: err.message })),
        ValidationError: (err) => Effect.fail(new InvalidEmailError({ email: input.email, message: err.message })),
    }),
)

Prefer Explicit Over Generic Errors

优先选择显式错误而非通用错误

Every distinct failure reason deserves its own error type. Don't collapse multiple failure modes into generic HTTP errors.
typescript
// WRONG - Generic errors lose information
export class NotFoundError extends Schema.TaggedError<NotFoundError>()(
    "NotFoundError",
    { message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

// Then mapping everything to it:
Effect.catchTags({
    UserNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
    ChannelNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
    MessageNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
})
// Frontend gets useless: { _tag: "NotFoundError", message: "Not found" }
// Which resource? User? Channel? Message? Can't tell!
typescript
// CORRECT - Explicit domain errors with rich context
export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
    "UserNotFoundError",
    { userId: UserId, message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class ChannelNotFoundError extends Schema.TaggedError<ChannelNotFoundError>()(
    "ChannelNotFoundError",
    { channelId: ChannelId, message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class SessionExpiredError extends Schema.TaggedError<SessionExpiredError>()(
    "SessionExpiredError",
    { sessionId: SessionId, expiredAt: Schema.DateTimeUtc, message: Schema.String },
    HttpApiSchema.annotations({ status: 401 }),
) {}

// Frontend can now show specific UI:
// - UserNotFoundError → "User doesn't exist"
// - ChannelNotFoundError → "Channel was deleted"
// - SessionExpiredError → "Your session expired. Please log in again."
See
references/error-patterns.md
for error remapping and retry patterns.
每个不同的失败原因都应拥有自己的错误类型。 不要将多种失败模式合并为通用HTTP错误。
typescript
// WRONG - Generic errors lose information
export class NotFoundError extends Schema.TaggedError<NotFoundError>()(
    "NotFoundError",
    { message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

// Then mapping everything to it:
Effect.catchTags({
    UserNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
    ChannelNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
    MessageNotFoundError: (err) => Effect.fail(new NotFoundError({ message: "Not found" })),
})
// Frontend gets useless: { _tag: "NotFoundError", message: "Not found" }
// Which resource? User? Channel? Message? Can't tell!
typescript
// CORRECT - Explicit domain errors with rich context
export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
    "UserNotFoundError",
    { userId: UserId, message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class ChannelNotFoundError extends Schema.TaggedError<ChannelNotFoundError>()(
    "ChannelNotFoundError",
    { channelId: ChannelId, message: Schema.String },
    HttpApiSchema.annotations({ status: 404 }),
) {}

export class SessionExpiredError extends Schema.TaggedError<SessionExpiredError>()(
    "SessionExpiredError",
    { sessionId: SessionId, expiredAt: Schema.DateTimeUtc, message: Schema.String },
    HttpApiSchema.annotations({ status: 401 }),
) {}

// Frontend can now show specific UI:
// - UserNotFoundError → "该用户不存在"
// - ChannelNotFoundError → "该频道已被删除"
// - SessionExpiredError → "您的会话已过期,请重新登录。"
错误重映射和重试模式请参考
references/error-patterns.md

Schema & Branded Types Pattern

Schema与品牌类型模式

Brand all entity IDs for type safety across service boundaries:
typescript
import { Schema } from "effect"

// Entity IDs - always branded
export const UserId = Schema.UUID.pipe(Schema.brand("@App/UserId"))
export type UserId = Schema.Schema.Type<typeof UserId>

export const OrganizationId = Schema.UUID.pipe(Schema.brand("@App/OrganizationId"))
export type OrganizationId = Schema.Schema.Type<typeof OrganizationId>

// Domain types - use Schema.Struct
export const User = Schema.Struct({
    id: UserId,
    email: Schema.String,
    name: Schema.String,
    organizationId: OrganizationId,
    createdAt: Schema.DateTimeUtc,
})
export type User = Schema.Schema.Type<typeof User>

// Input types for mutations
export const CreateUserInput = Schema.Struct({
    email: Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
    name: Schema.String.pipe(Schema.minLength(1)),
    organizationId: OrganizationId,
})
export type CreateUserInput = Schema.Schema.Type<typeof CreateUserInput>
When NOT to brand:
  • Simple strings that don't cross service boundaries (URLs, file paths)
  • Primitive config values
See
references/schema-patterns.md
for transforms and advanced patterns.
为所有实体ID添加品牌标识,以确保跨服务边界的类型安全性:
typescript
import { Schema } from "effect"

// Entity IDs - always branded
export const UserId = Schema.UUID.pipe(Schema.brand("@App/UserId"))
export type UserId = Schema.Schema.Type<typeof UserId>

export const OrganizationId = Schema.UUID.pipe(Schema.brand("@App/OrganizationId"))
export type OrganizationId = Schema.Schema.Type<typeof OrganizationId>

// Domain types - use Schema.Struct
export const User = Schema.Struct({
    id: UserId,
    email: Schema.String,
    name: Schema.String,
    organizationId: OrganizationId,
    createdAt: Schema.DateTimeUtc,
})
export type User = Schema.Schema.Type<typeof User>

// Input types for mutations
export const CreateUserInput = Schema.Struct({
    email: Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
    name: Schema.String.pipe(Schema.minLength(1)),
    organizationId: OrganizationId,
})
export type CreateUserInput = Schema.Schema.Type<typeof CreateUserInput>
无需添加品牌标识的场景:
  • 不跨服务边界的简单字符串(URL、文件路径)
  • 原始配置值
转换和高级模式请参考
references/schema-patterns.md

Function Pattern with Effect.fn

使用Effect.fn的函数模式

Always use
Effect.fn
for service methods. This provides automatic tracing with proper span names:
typescript
// CORRECT - Effect.fn with descriptive name
const findById = Effect.fn("UserService.findById")(function* (id: UserId) {
    yield* Effect.annotateCurrentSpan("userId", id)
    const user = yield* repo.findById(id)
    return user
})

// CORRECT - Effect.fn with multiple parameters
const transfer = Effect.fn("AccountService.transfer")(
    function* (fromId: AccountId, toId: AccountId, amount: number) {
        yield* Effect.annotateCurrentSpan("fromId", fromId)
        yield* Effect.annotateCurrentSpan("toId", toId)
        yield* Effect.annotateCurrentSpan("amount", amount)
        // ...
    }
)
始终为服务方法使用
Effect.fn
。它会提供带有正确跨度名称的自动追踪功能:
typescript
// CORRECT - Effect.fn with descriptive name
const findById = Effect.fn("UserService.findById")(function* (id: UserId) {
    yield* Effect.annotateCurrentSpan("userId", id)
    const user = yield* repo.findById(id)
    return user
})

// CORRECT - Effect.fn with multiple parameters
const transfer = Effect.fn("AccountService.transfer")(
    function* (fromId: AccountId, toId: AccountId, amount: number) {
        yield* Effect.annotateCurrentSpan("fromId", fromId)
        yield* Effect.annotateCurrentSpan("toId", toId)
        yield* Effect.annotateCurrentSpan("amount", amount)
        // ...
    }
)

Layer Composition

Layer组合

Declare dependencies in the service, not at usage sites:
typescript
// CORRECT - dependencies in service definition
export class OrderService extends Effect.Service<OrderService>()("OrderService", {
    accessors: true,
    dependencies: [
        UserService.Default,
        ProductService.Default,
        PaymentService.Default,
    ],
    effect: Effect.gen(function* () {
        const users = yield* UserService
        const products = yield* ProductService
        const payments = yield* PaymentService
        // ...
    }),
}) {}

// At app root - simple merge
const AppLive = Layer.mergeAll(
    OrderService.Default,
    // Infrastructure layers (intentionally not in dependencies)
    DatabaseLive,
    RedisLive,
)
See
references/layer-patterns.md
for testing layers and config-dependent layers.
在服务中声明依赖项,而非在使用位置:
typescript
// CORRECT - dependencies in service definition
export class OrderService extends Effect.Service<OrderService>()("OrderService", {
    accessors: true,
    dependencies: [
        UserService.Default,
        ProductService.Default,
        PaymentService.Default,
    ],
    effect: Effect.gen(function* () {
        const users = yield* UserService
        const products = yield* ProductService
        const payments = yield* PaymentService
        // ...
    }),
}) {}

// At app root - simple merge
const AppLive = Layer.mergeAll(
    OrderService.Default,
    // Infrastructure layers (intentionally not in dependencies)
    DatabaseLive,
    RedisLive,
)
测试层和依赖配置的层请参考
references/layer-patterns.md

Option Handling

Option处理

Never use
Option.getOrThrow
. Always handle both cases explicitly:
typescript
// CORRECT - explicit handling
yield* Option.match(maybeUser, {
    onNone: () => Effect.fail(new UserNotFoundError({ userId, message: "Not found" })),
    onSome: (user) => Effect.succeed(user),
})

// CORRECT - with getOrElse for defaults
const name = Option.getOrElse(maybeName, () => "Anonymous")

// CORRECT - Option.map for transformations
const upperName = Option.map(maybeName, (n) => n.toUpperCase())
永远不要使用
Option.getOrThrow
。始终显式处理两种情况:
typescript
// CORRECT - explicit handling
yield* Option.match(maybeUser, {
    onNone: () => Effect.fail(new UserNotFoundError({ userId, message: "未找到" })),
    onSome: (user) => Effect.succeed(user),
})

// CORRECT - with getOrElse for defaults
const name = Option.getOrElse(maybeName, () => "匿名用户")

// CORRECT - Option.map for transformations
const upperName = Option.map(maybeName, (n) => n.toUpperCase())

Effect Atom (Frontend State)

Effect Atom(前端状态管理)

Effect Atom provides reactive state management for React with Effect integration.
Effect Atom为React提供了集成Effect的响应式状态管理。

Basic Atoms

基础原子

typescript
import { Atom } from "@effect-atom/atom-react"

// Define atoms OUTSIDE components
const countAtom = Atom.make(0)

// Use keepAlive for global state that should persist
const userPrefsAtom = Atom.make({ theme: "dark" }).pipe(Atom.keepAlive)

// Atom families for per-entity state
const modalAtomFamily = Atom.family((type: string) =>
    Atom.make({ isOpen: false }).pipe(Atom.keepAlive)
)
typescript
import { Atom } from "@effect-atom/atom-react"

// Define atoms OUTSIDE components
const countAtom = Atom.make(0)

// Use keepAlive for global state that should persist
const userPrefsAtom = Atom.make({ theme: "dark" }).pipe(Atom.keepAlive)

// Atom families for per-entity state
const modalAtomFamily = Atom.family((type: string) =>
    Atom.make({ isOpen: false }).pipe(Atom.keepAlive)
)

React Integration

React集成

typescript
import { useAtomValue, useAtomSet, useAtom, useAtomMount } from "@effect-atom/atom-react"

function Counter() {
    const count = useAtomValue(countAtom)           // Read only
    const setCount = useAtomSet(countAtom)          // Write only
    const [value, setValue] = useAtom(countAtom)    // Read + write

    return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

// Mount side-effect atoms without reading value
function App() {
    useAtomMount(keyboardShortcutsAtom)
    return <>{children}</>
}
typescript
import { useAtomValue, useAtomSet, useAtom, useAtomMount } from "@effect-atom/atom-react"

function Counter() {
    const count = useAtomValue(countAtom)           // 仅读取
    const setCount = useAtomSet(countAtom)          // 仅写入
    const [value, setValue] = useAtom(countAtom)    // 读取+写入

    return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

// 挂载副作用原子而不读取值
function App() {
    useAtomMount(keyboardShortcutsAtom)
    return <>{children}</>
}

Handling Results with Result.builder

使用Result.builder处理结果

Use
Result.builder
for rendering effectful atom results. It provides chainable error handling with
onErrorTag
:
typescript
import { Result } from "@effect-atom/atom-react"

function UserProfile() {
    const userResult = useAtomValue(userAtom) // Result<User, Error>

    return Result.builder(userResult)
        .onInitial(() => <div>Loading...</div>)
        .onErrorTag("NotFoundError", () => <div>User not found</div>)
        .onError((error) => <div>Error: {error.message}</div>)
        .onSuccess((user) => <div>Hello, {user.name}</div>)
        .render()
}
**使用
Result.builder
**渲染有副作用的原子结果。它通过
onErrorTag
提供可链式调用的错误处理:
typescript
import { Result } from "@effect-atom/atom-react"

function UserProfile() {
    const userResult = useAtomValue(userAtom) // Result<User, Error>

    return Result.builder(userResult)
        .onInitial(() => <div>加载中...</div>)
        .onErrorTag("NotFoundError", () => <div>用户未找到</div>)
        .onError((error) => <div>错误:{error.message}</div>)
        .onSuccess((user) => <div>你好,{user.name}</div>)
        .render()
}

Atoms with Side Effects

带副作用的原子

typescript
const scrollYAtom = Atom.make((get) => {
    const onScroll = () => get.setSelf(window.scrollY)

    window.addEventListener("scroll", onScroll)
    get.addFinalizer(() => window.removeEventListener("scroll", onScroll)) // REQUIRED

    return window.scrollY
}).pipe(Atom.keepAlive)
See
references/effect-atom-patterns.md
for complete patterns including families, localStorage, and anti-patterns.
typescript
const scrollYAtom = Atom.make((get) => {
    const onScroll = () => get.setSelf(window.scrollY)

    window.addEventListener("scroll", onScroll)
    get.addFinalizer(() => window.removeEventListener("scroll", onScroll)) // 必须添加

    return window.scrollY
}).pipe(Atom.keepAlive)
完整模式包括原子家族、localStorage和反模式,请参考
references/effect-atom-patterns.md

RPC & Cluster Patterns

RPC与集群模式

For RPC contracts and cluster workflows, see:
  • references/rpc-cluster-patterns.md
    - RpcGroup, Workflow.make, Activity patterns
关于RPC契约和集群工作流,请参考:
  • references/rpc-cluster-patterns.md
    - RpcGroup、Workflow.make、Activity模式

Anti-Patterns (Forbidden)

反模式(禁止使用)

These patterns are never acceptable:
typescript
// FORBIDDEN - runSync/runPromise inside services
const result = Effect.runSync(someEffect) // Never do this

// FORBIDDEN - throw inside Effect.gen
yield* Effect.gen(function* () {
    if (bad) throw new Error("No!") // Use Effect.fail instead
})

// FORBIDDEN - catchAll losing type info
yield* effect.pipe(Effect.catchAll(() => Effect.fail(new GenericError())))

// FORBIDDEN - console.log
console.log("debug") // Use Effect.log

// FORBIDDEN - process.env directly
const key = process.env.API_KEY // Use Config.string("API_KEY")

// FORBIDDEN - null/undefined in domain types
type User = { name: string | null } // Use Option<string>
See
references/anti-patterns.md
for the complete list with rationale.
这些模式绝对不允许
typescript
// FORBIDDEN - runSync/runPromise inside services
const result = Effect.runSync(someEffect) // 绝对不要这样做

// FORBIDDEN - throw inside Effect.gen
yield* Effect.gen(function* () {
    if (bad) throw new Error("错误!") // 请使用Effect.fail替代
})

// FORBIDDEN - catchAll losing type info
yield* effect.pipe(Effect.catchAll(() => Effect.fail(new GenericError())))

// FORBIDDEN - console.log
console.log("调试信息") // 请使用Effect.log

// FORBIDDEN - process.env directly
const key = process.env.API_KEY // 请使用Config.string("API_KEY")

// FORBIDDEN - null/undefined in domain types
type User = { name: string | null } // 请使用Option<string>
完整的禁止模式列表及原因请参考
references/anti-patterns.md

Observability

可观测性

typescript
// Structured logging
yield* Effect.log("Processing order", { orderId, userId, amount })

// Metrics
const orderCounter = Metric.counter("orders_processed")
yield* Metric.increment(orderCounter)

// Config with validation
const config = Config.all({
    port: Config.integer("PORT").pipe(Config.withDefault(3000)),
    apiKey: Config.secret("API_KEY"),
    maxRetries: Config.integer("MAX_RETRIES").pipe(
        Config.validate({ message: "Must be positive", validation: (n) => n > 0 })
    ),
})
See
references/observability-patterns.md
for metrics and tracing patterns.
typescript
// 结构化日志
yield* Effect.log("处理订单", { orderId, userId, amount })

// 指标
const orderCounter = Metric.counter("orders_processed")
yield* Metric.increment(orderCounter)

// 带验证的配置
const config = Config.all({
    port: Config.integer("PORT").pipe(Config.withDefault(3000)),
    apiKey: Config.secret("API_KEY"),
    maxRetries: Config.integer("MAX_RETRIES").pipe(
        Config.validate({ message: "必须为正数", validation: (n) => n > 0 })
    ),
})
指标和追踪模式请参考
references/observability-patterns.md

Reference Files

参考文件

For detailed patterns, consult these reference files in the
references/
directory:
  • service-patterns.md
    - Service definition, Effect.fn, Context.Tag exceptions
  • error-patterns.md
    - Schema.TaggedError, error remapping, retry patterns
  • schema-patterns.md
    - Branded types, transforms, Schema.Class
  • layer-patterns.md
    - Dependency composition, testing layers
  • rpc-cluster-patterns.md
    - RpcGroup, Workflow, Activity patterns
  • effect-atom-patterns.md
    - Atom, families, React hooks, Result handling
  • anti-patterns.md
    - Complete list of forbidden patterns
  • observability-patterns.md
    - Logging, metrics, config patterns
如需详细模式,请查阅
references/
目录下的以下参考文件:
  • service-patterns.md
    - 服务定义、Effect.fn、Context.Tag例外情况
  • error-patterns.md
    - Schema.TaggedError、错误重映射、重试模式
  • schema-patterns.md
    - 品牌类型、转换、Schema.Class
  • layer-patterns.md
    - 依赖组合、测试层
  • rpc-cluster-patterns.md
    - RpcGroup、Workflow、Activity模式
  • effect-atom-patterns.md
    - Atom、原子家族、React钩子、结果处理
  • anti-patterns.md
    - 完整的禁止模式列表
  • observability-patterns.md
    - 日志、指标、配置模式