OTAKU / Languages

Reference

Languages and tooling

The philosophy is language-neutral; the vocabulary is not. Each ecosystem has its own name for a value object, its own way to express a collection, and its own tool to prove that dependencies point inwards. Five languages are worked through in detail; the rules are the same in all of them.

The same concepts, four vocabularies

ConceptPHPC#JavaC++
Value objectfinal class + private constructorrecord or sealed classrecordclass + operator==class + private constructor
First-class collectionclass wrapping arrayclass wrapping IReadOnlyList<T>class wrapping List<T>class wrapping std::vector<T>class wrapping readonly T[]
Contractinterfaceinterfaceinterfaceabstract class, pure virtualinterface
Domain failureexception extending DomainExceptionexception, or Result<T>unchecked exceptionexception, or std::expectedexception, or a Result<T> union
Package boundaryComposer packageNuGet package / projectMaven moduleCMake target / Conan packageworkspace package (npm/pnpm)

PHP

The reference implementation, and the language where OTAKU was first applied. Attributes replace annotations; declare(strict_types=1) is mandatory; a class never closes the PHP tag.

  • Value objects: final class, private constructor, named constructors.
  • Collections: a class with an array field and behaviour — never a bare array in a signature.
  • Ports: interfaces in the domain namespace; adapters in Infrastructure.
  • Tooling: PHP_CodeSniffer, PHPStan/Psalm, deptrac, PHPUnit, Infection.

C#

C# gives you record, init and required, which make value objects almost free. The risk is the opposite of PHP: it is easy to make everything a record and lose behaviour.

  • Value objects: sealed record with a validating constructor, or a sealed class with IEquatable<T>.
  • Collections: wrap IReadOnlyList<T>; expose behaviour, not IEnumerable.
  • Ports: interfaces in the domain project; adapters in an infrastructure project with no reference back.
  • Tooling: Roslyn analyzers, StyleCop, NetArchTest, dotnet test.

Java

record covers the value object case; the hard part is module boundaries, because Maven’s default is a single classpath where everything sees everything.

  • Value objects: record with a compact constructor that validates.
  • Collections: wrap List<T> and never return the internal list.
  • Ports: interfaces in the domain package; ArchUnit enforces that the domain package imports nothing from infrastructure.
  • Tooling: Checkstyle, PMD, ArchUnit, jdeps, JUnit 5.

C++

The most honest language about costs, and the strictest about ownership. There is no reflection and no framework to hide behind — which makes OTAKU easier to apply and harder to fake.

  • Value objects: class with private state and operator== defaulted; pass by value or const&.
  • Collections: wrap std::vector<T>; expose iterators only if the invariant survives.
  • Ports: abstract class with pure virtual methods and a virtual destructor; no implementation in the header.
  • Tooling: clang-tidy, include-what-you-use, CMake target visibility, sanitizers, std::expected for expected failures.

TypeScript

TypeScript is the fifth language of the guide for a practical reason: it is where most object-oriented code is written today, and it has everything the philosophy needs — classes, interfaces, generics, exceptions and a package manager. The language also makes two rules easy to break, so they need attention.

  • Value objects: a class with a private constructor and static factory, because readonly alone does not validate.
  • Collections: wrap readonly T[]; never return the internal array.
  • Contracts: interface for ports; keep infrastructure types out of domain signatures.
  • Pitfall — structural typing: two identical shapes are interchangeable. Add a brand (declare const brand: unique symbol) when identity matters.
  • Pitfall — any: it is the local equivalent of mixed. Rule 38 applies: only at explicit serialization boundaries.
  • Tooling: ESLint complexity and max-lines-per-function, tsc --strict, ts-arch for dependency rules, Vitest for tests.

What does not translate

  • Annotations/attributes have no equivalent in C++; in the other three they are infrastructure and belong outside the domain.
  • Garbage collection vs ownership changes how aggregates are copied but not how they are designed.
  • Exceptions vs result types changes the signature of failure, not the rule that failure must be explicit.
  • Package managers differ in how an assembly is declared; the concept of “no logic, only dependencies” does not.

If your language can express a class, an interface and a package, the philosophy applies. If it cannot, say so — and do not pretend.