Architecting Cross-Platform Domain Models with Haxe Enums
A technical guide on using Haxe algebraic data types to build type-safe, serializable domain models across JS, Neko, and C++ targets.
21 Sept 2026, 11:49 UTC

The Problem: State Consistency Across Targets
When building applications that target multiple platforms (such as JavaScript, Neko, and C++), maintaining a consistent domain model is challenging. Discrepancies in how different targets handle types can lead to runtime crashes or silent data corruption, especially when serializing state across a network or saving it to disk.
The goal is to create a domain representation that is finite, type-safe, and serializable, ensuring that adding a new state to the system forces a compile-time update across all consuming modules.
Smallest Suitable Design
The most efficient way to achieve this in Haxe is using Enums (Algebraic Data Types). The smallest suitable design involves a single enum per domain concept, utilizing nested enums only when a sub-state requires its own finite set of values. This keeps the API surface minimal and self-documenting.
// src/model/AppState.hx
package model;
/**
* Finite set of application states.
* Using a standard enum ensures exhaustive switch checks.
*/
enum AppState {
Idle,
Loading,
Ready,
Error(String) // Enums can carry associated data
}
/**
* Factory to handle the trust boundary during deserialization.
*/
class StateFactory {
public static function fromString(s: String): AppState {
switch (s) {
case "idle": return Idle;
case "loading": return Loading;
case "ready": return Ready;
case "error": return Error("Unknown error state");
default:
// Log and fall back to a safe state to prevent crashes
trace("Unknown AppState: " + s + " - defaulting to Idle");
return Idle;
}
}
}
Trust and Data Boundaries
To maintain integrity, a strict boundary must exist between where data is created and where it is consumed.
- Creation Boundary: Only trusted factory classes (like
StateFactory) should convert raw external data (JSON, Strings) into Enum types. This prevents the rest of the application from dealing with malformed or unexpected input. - Consumption Boundary: Business logic modules receive the Enum and use pattern matching. Because Haxe enums are immutable, consumers cannot accidentally mutate the state, ensuring the domain model remains a "source of truth."
Operational Checks
Compile-Time Exhaustiveness
The primary benefit of this architecture is the switch statement. If a developer adds a new state (e.g., Maintaining) to the AppState enum, the Haxe compiler will trigger a warning or error in every switch block that does not handle the new case. This eliminates a common class of "forgotten case" bugs.
Runtime Validation
Since serialization differs across targets (JS vs. Neko), you must validate data during deserialization. Use haxe.Json for basic transport, but always pass the result through a factory that maps the input to the Enum. This ensures that an unknown value from a newer version of the software does not crash an older client.
Failure Modes and Limitations
Malformed Data: If an external source provides a value not defined in the Enum, the default case in the factory must map this to a safe fallback state (e.g., Idle) and log the incident. This prevents the application from entering an undefined state.
Performance: In extremely large enums (hundreds of cases), some targets may see a slight performance dip in switch implementation. In such rare cases, consider splitting the domain into several smaller, related enums.
Verification and Rollback
To verify the implementation, perform the following checks:
- Cross-Target Test: Compile the project for JS and C++. Serialize an Enum to JSON and deserialize it back; confirm the state remains identical.
- Injection Test: Pass an invalid string to the
StateFactoryand verify that thedefaultcase is triggered without throwing an exception. - Exhaustiveness Test: Add a dummy case to the Enum and verify that the compiler flags all incomplete
switchstatements.
Rollback: Because this design changes the type system of the domain model, rolling back requires reverting the Enum definition and the corresponding factory mappings to the previous version to maintain compatibility with persisted data.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.