144-java-data-oriented-programming
npx machina-cli add skill jabrena/cursor-rules-java/144-java-data-oriented-programming --openclawJava Data-Oriented Programming Best Practices
Identify and apply data-oriented programming best practices in Java to improve code clarity, maintainability, and predictability by strictly separating data structures from behavior and ensuring all data transformations are explicit, pure, and traceable.
Core areas: Records for immutable data carriers over mutable POJOs, data-behavior separation with pure static utility classes holding operations, pure functions for data transformation that depend only on inputs and produce no side effects, flat denormalized data structures with ID-based references over deep nesting, generic Map<String, Object> representations for dynamic schemas converted to specific types when needed, Optional<T> and custom result types in validation functions instead of throwing exceptions, generic DataStore<ID, T> interfaces for flexible data access layers, composable Function<A, B> pipelines using andThen for explicit and traceable multi-step transformations.
Prerequisites: Run ./mvnw compile before applying any changes. If compilation fails, stop immediately — do not proceed until the project compiles successfully.
Multi-step scope: Step 1 validates the project compiles. Step 2 categorizes data-oriented programming opportunities by impact (CRITICAL, MAINTAINABILITY, PERFORMANCE, CLARITY) and area (data-behavior separation, immutability violations, impure functions, nested data structures, generic vs specific types, validation approaches). Step 3 separates data from behavior by extracting Records as pure data carriers and moving operations to static utility classes. Step 4 establishes immutability throughout the codebase using Records, List.copyOf(), and Collections.unmodifiableList(). Step 5 extracts pure static functions for all data processing and transformation operations to eliminate side effects. Step 6 flattens complex nested object hierarchies using ID references and lookup patterns in flat Map-based stores. Step 7 implements generic and flexible data types starting with Map<String, Object> for dynamic schemas, converting to specific record types via explicit converter classes with validation. Step 8 establishes validation boundaries using pure functions that return Optional<String> error messages or result types instead of throwing exceptions for business validation. Step 9 designs flexible generic DataStore<ID, T> interfaces to decouple access logic from storage implementations. Step 10 creates composable data transformation pipelines using Function.andThen() with explicit tracing at each step. Step 11 runs ./mvnw clean verify to confirm all tests pass after changes.
Before applying changes: Read the reference for detailed good/bad examples, constraints, and safeguards for each data-oriented programming pattern.
Reference
For detailed guidance, examples, and constraints, see references/144-java-data-oriented-programming.md.
Source
git clone https://github.com/jabrena/cursor-rules-java/blob/main/skills/144-java-data-oriented-programming/SKILL.mdView on GitHub Overview
Learn to separate data structures from behavior in Java, using records for immutable data and pure functions. The approach favors flat, ID-based data, generic Map schemas converted to typed records, and pure validation to build flexible, testable data stores.
How This Skill Works
Records serve as pure data carriers while static utility classes hold data operations. Transformations are composed as pure functions that depend only on inputs, chained with andThen to create auditable pipelines. Generic Map<String, Object> schemas are converted to specific types via explicit converters with validation, enabling decoupled data access layers.
When to Use It
- Starting a new Java project or module that benefits from strict data-behavior separation and immutable data carriers.
- Flattening complex nested objects into ID-based references for easier data traversal and storage.
- Avoiding exceptions for business validation by returning Optional<String> errors or custom result types.
- Designing a flexible, generic data access layer with DataStore<ID, T> interfaces.
- Building composable, auditable data transformation pipelines using Function.andThen with explicit tracing.
Quick Start
- Step 1: Ensure prerequisites are met and run ./mvnw compile to verify the project builds.
- Step 2: Extract data into Records as pure data carriers and move logic into static pure utility classes.
- Step 3: Introduce Map<String, Object> schemas, add converters with validation, and wire a DataStore<ID, T> plus a Function.andThen pipeline.
Best Practices
- Prefer Records for immutable data carriers over mutable POJOs.
- Separate data from behavior; place operations in pure static utility classes.
- Implement data transformations as pure functions that depend only on inputs and produce outputs.
- Flatten nested structures into ID-based references and use converters to materialize specific types.
- Use Optional<T> and custom result types for validation rather than throwing exceptions; define generic DataStore<ID, T> interfaces.
Example Use Cases
- Domain example: process orders using flat maps, IDs, and converters to typed records.
- Converter class that maps Map<String, Object> into a typed Record with validation checks.
- Validation scenario where errors are returned as Optional<String> instead of throwing.
- Composable pipeline example: transform data through multiple pure steps using Function.andThen().
- DataStore decoupled storage access from retrieval logic via a generic DataStore<ID, T> interface.