ADR-0002: Foundation - Adopt LanguageExt as the Functional Base Library
Context and Problem
Section titled “Context and Problem”Functorium aims to compose business logic using functional programming patterns on top of C# and to control side effects. A simple Result<T> type alone cannot express requirements like “apply a 3-second timeout to an external payment API call, retry twice on failure, and compose with the next step on success.” This inevitably leads to falling back on try-catch and manual retry loops, burying business flow under infrastructure code.
A functional library adopted at the framework level affects every layer from Domain to Adapter. Therefore, it must support not only error-handling types like Fin/Validation, but also deferred execution of side effects via IO monad, composition of compound effects through monad transformers (FinT), and type-safe handling of infrastructure concerns such as Timeout/Retry/Fork/Bracket.
Considered Options
Section titled “Considered Options”- LanguageExt
- CSharpFunctionalExtensions
- OneOf
- Custom implementation
Decision
Section titled “Decision”Chosen option: “LanguageExt”. Among the four options reviewed, it is the only one that provides all the functional abstractions Functorium requires — error handling (Fin/Validation), side-effect deferral (IO), compound effect composition (FinT), LINQ query syntax, and Timeout/Retry/Fork/Bracket — in a single library.
Consequences
Section titled “Consequences”- Good, because error handling (Fin/Validation), side-effect control (IO), and compound effect composition (FinT) are unified in a single library without combining separate packages, maintaining API consistency.
- Good, because LINQ query syntax enables declarative “validate -> domain logic -> persist -> publish event” pipelines, making business flow visible directly in code.
- Good, because infrastructure concerns like Timeout, Retry, Fork, and Bracket compose on top of the IO type, eliminating try-catch and manual retry loops.
- Bad, because many concepts are borrowed from Haskell —
FinT,Eff,Aff, etc. — so C# developers without functional programming experience need significant learning time to grasp monad transformers. - Bad, because LanguageExt is a large library containing hundreds of types and extension methods, and unused types like Either and Eff are also included in the dependency.
Confirmation
Section titled “Confirmation”- Verify that core packages (
Functorium.Core,Functorium.Application, etc.) reference LanguageExt. - Verify through pipeline tests that side effects are deferred and composed via the IO monad.
Pros and Cons of the Options
Section titled “Pros and Cons of the Options”LanguageExt
Section titled “LanguageExt”- Good, because Fin, Validation, Option, Either, IO, FinT, and all other types needed across every Functorium layer are provided in a single package.
- Good, because it implements LINQ
SelectMany, enabling monadic composition viafrom ... in ... selectsyntax in a form familiar to C# developers. - Good, because Timeout/Retry/Fork/Bracket compose type-safely on top of the IO monad, preventing infrastructure concerns from invading business logic.
- Bad, because major version upgrades may introduce breaking changes, resulting in high migration costs that impact the entire framework.
- Bad, because LanguageExt experienced developers are rare in the .NET ecosystem, limiting the hiring pool and requiring additional training for new team members.
CSharpFunctionalExtensions
Section titled “CSharpFunctionalExtensions”- Good, because it provides DDD-friendly types like Result, Maybe, and ValueObject, with a low learning curve enabling quick team adoption.
- Good, because high NuGet download counts indicate rich community support and references.
- Bad, because there is no IO monad, so side effects like “retry on failure after payment API call” cannot be composed type-safely and ultimately revert to try-catch.
- Bad, because there are no monad transformers, making it structurally impossible to compose compound effects like
Fin+IOin a single pipeline.
- Good, because it provides a lightweight discriminated union with
Matchfor pattern matching, and is simple to adopt. - Bad, because it does not support LINQ composition, so multi-step business pipelines cannot be constructed declaratively.
- Bad, because there are no monadic operations like
Bind/Map, requiring manual branching to connect each step’s result to the next. - Bad, because positional type parameters in the form
OneOf<T0, T1, T2>cannot express a structural error classification system likeDomainErrorType.
Custom Implementation
Section titled “Custom Implementation”- Good, because only the types Functorium actually uses can be implemented as a lightweight solution, eliminating unnecessary dependencies.
- Good, because there is no risk of breaking changes from external library version upgrades.
- Bad, because the IO monad, monad transformers (
FinT), and LINQSelectManyintegration must all be implemented and verified from scratch, meaning thousands of lines of core infrastructure code must be self-maintained. - Bad, because bugs in edge cases (thread safety, stack overflow prevention, etc.) can arise, and it is difficult to establish reliability without community validation.
Related Information
Section titled “Related Information”- Related commit:
cda0a338feat(functorium): Add core library package references and source structure - Related commit:
d304ab40refactor(ecommerce-ddd): Functional refactoring of PlaceOrderCommand Handle method - Related docs:
Docs.Site/src/content/docs/spec/