Skip to content

Instantly share code, notes, and snippets.

@wware
Last active August 20, 2025 19:25
Show Gist options
  • Save wware/45e6b4bbe15b48467de4024f56464ad7 to your computer and use it in GitHub Desktop.
Save wware/45e6b4bbe15b48467de4024f56464ad7 to your computer and use it in GitHub Desktop.

Revisions

  1. wware revised this gist Aug 19, 2025. 1 changed file with 56 additions and 1 deletion.
    57 changes: 56 additions & 1 deletion 0_README.md
    Original file line number Diff line number Diff line change
    @@ -179,4 +179,59 @@ The PostgreSQL container is still running:
    ```bash
    docker ps # Check if marten-postgres is running
    # Connect: docker exec -it marten-postgres psql -U postgres -d marten_test
    ```
    ```

    ---

    ## OTHER STUFF

    Your gist captures the essential concepts very well! You've got all the important parts. I'd suggest adding just a few missing pieces to make it complete:

    **1. Add the actual code files** - You mention "Both java files go in src/main/java/com/example/" but don't show them. Include:

    ```java
    // User.java
    package com.example;
    import org.babyfish.jimmer.sql.*;
    import java.util.UUID;
    import java.time.LocalDateTime;

    @Entity
    public interface User {
    @Id
    UUID id();
    String name();
    String email();
    LocalDateTime createdAt();
    }
    ```

    **2. Include the pom.xml** since that was crucial for getting Jimmer working:

    ```xml
    <!-- Key dependencies section -->
    <dependency>
    <groupId>org.babyfish.jimmer</groupId>
    <artifactId>jimmer-sql</artifactId>
    <version>0.8.138</version>
    </dependency>
    ```

    **3. Add one important insight you discovered** - the "SQL backwards" LINQ concept:

    ```
    LINQ is "SQL backwards":
    - SQL: SELECT name FROM users WHERE age > 25 ORDER BY name
    - LINQ: users.Where(u => u.Age > 25).OrderBy(u => u.Name).Select(u => u.Name)
    ```

    **4. Mention the Docker PostgreSQL setup** since that was essential:

    ```bash
    docker run --name marten-postgres \
    -e POSTGRES_PASSWORD=password \
    -e POSTGRES_DB=marten_test \
    -p 5432:5432 -d postgres:15
    ```

    Everything else is spot on! You've captured the key insights about document storage, code generation, and the conceptual similarities between Marten and Jimmer.
  2. wware created this gist Aug 19, 2025.
    3 changes: 3 additions & 0 deletions .gitignore
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    .gradle/
    target/
    # btw the java files go in src/main/java/com/example/*.java
    182 changes: 182 additions & 0 deletions 0_README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,182 @@
    # Marten Exploration Summary

    ## What is Marten?

    Marten is a document database library for .NET that uses PostgreSQL as its storage engine. It provides a NoSQL-like API while leveraging PostgreSQL's JSONB capabilities and ACID compliance.

    **Key Characteristics:**
    - Stores .NET objects as JSON documents in PostgreSQL JSONB columns
    - Provides LINQ query support that translates to PostgreSQL JSON operators
    - Supports event sourcing patterns
    - Uses optimistic concurrency control
    - Handles serialization with opinionated defaults (camel-case properties, enum strings)

    Both java files go in `src/main/java/com/example/`

    ## Core Concepts

    ### Document Storage Pattern
    Instead of traditional relational tables, Marten stores entire objects as JSON documents:

    ```sql
    -- Traditional relational approach
    CREATE TABLE users (
    id UUID,
    name VARCHAR(100),
    email VARCHAR(100),
    created_at TIMESTAMP
    );

    -- Marten approach
    CREATE TABLE users (
    id UUID PRIMARY KEY,
    data JSONB NOT NULL -- entire object stored here
    );
    ```

    ### LINQ to PostgreSQL JSON
    Marten translates C# LINQ expressions into PostgreSQL JSON operators:

    ```csharp
    // C# LINQ
    session.Query<User>()
    .Where(u => u.Email.Contains("@example.com"))
    .OrderBy(u => u.Name)

    // Becomes PostgreSQL
    SELECT data FROM users
    WHERE data->>'email' LIKE '%@example.com%'
    ORDER BY data->>'name'
    ```

    ### PostgreSQL JSON Operators
    - `->>` : Extract field as text (`data->>'email'`)
    - `->` : Extract field as JSON (`data->'address'`)
    - `@>` : Contains (`data @> '{"name": "Alice"}'`)
    - `?` : Key exists (`data ? 'email'`)

    ## Working Example: Raw PostgreSQL JSONB

    We built a simple example showing what Marten does under the hood:

    ```java
    // Store a document
    Map<String, Object> user = new HashMap<>();
    user.put("name", "Alice");
    user.put("email", "[email protected]");

    String userJson = mapper.writeValueAsString(user);
    PreparedStatement insert = conn.prepareStatement(
    "INSERT INTO users (id, data) VALUES (?::uuid, ?::jsonb)");
    insert.setString(1, UUID.randomUUID().toString());
    insert.setString(2, userJson);

    // Query with JSON operators
    PreparedStatement query = conn.prepareStatement(
    "SELECT data FROM users WHERE data->>'email' LIKE ?");
    query.setString(1, "%@example.com%");
    ```

    **Key insight:** Marten eliminates this JDBC/JSON boilerplate with a clean object-oriented API.

    ## Alternative: Jimmer (Java/Kotlin)

    Jimmer provides similar functionality to Marten for the JVM ecosystem:

    ### Entity Definition
    ```java
    @Entity
    public interface User {
    @Id
    UUID id();
    String name();
    String email();
    LocalDateTime createdAt();
    }
    ```

    ### Object Creation
    ```java
    User user = UserDraft.$.produce(draft -> {
    draft.setName("Alice");
    draft.setEmail("[email protected]");
    draft.setCreatedAt(LocalDateTime.now());
    });
    ```

    ### Code Generation
    Like Marten, Jimmer uses code generation at compile time:
    - Generates `UserDraft` for object creation
    - Generates `UserTable` for type-safe querying
    - Handles immutable object implementations

    ## Development Environment Setup

    ### Java/Jimmer Path (Working)
    ```bash
    # Prerequisites
    sudo apt install openjdk-17-jdk maven

    # Project structure
    mkdir marten-experiment && cd marten-experiment
    # Create pom.xml with Jimmer dependencies
    # Create entity interfaces
    # Maven handles code generation automatically
    mvn compile && mvn exec:java -Dexec.mainClass="com.example.JimmerExample"
    ```

    ### .NET/Marten Path (Blocked)
    Corporate laptop restrictions prevented .NET SDK installation. Options to explore:
    - Request IT approval for .NET SDK
    - Use WSL2 with different permissions
    - Try Mono (outdated, not recommended)
    - Use Visual Studio if available

    ## Key Patterns Learned

    ### 1. Document vs Relational Storage
    - **Relational**: Normalize data across tables with foreign keys
    - **Document**: Store complete objects as JSON, query within documents
    - **Hybrid**: Marten/Jimmer allow both patterns in the same database

    ### 2. Code Generation Benefits
    - Define data shape once (class/interface)
    - Generated code handles:
    - Object creation/mutation
    - Serialization/deserialization
    - Type-safe query builders
    - Database schema management

    ### 3. Query Translation
    - Write queries in host language (C# LINQ, Java builder pattern)
    - Library translates to optimized SQL with JSON operators
    - Deferred execution - builds expression trees, executes when enumerated

    ## Event Sourcing Context

    Marten is popular for event sourcing because:
    - Events are naturally document-shaped (JSON)
    - PostgreSQL provides strong consistency guarantees
    - Built-in stream aggregation and projection capabilities
    - Can mix event data with traditional relational data

    ## Next Steps

    1. **Get .NET working** - Either through IT approval or alternative approach
    2. **Try real Marten examples** - Document storage, event sourcing, projections
    3. **Compare with current architecture** - Understand migration implications
    4. **Explore Marten's "warts"** - Concurrency handling, performance characteristics, serialization edge cases

    ## Working Code

    The Jimmer experiment is ready to extend:
    - Location: `~/marten-hack/`
    - Run: `mvn exec:java -Dexec.mainClass="com.example.JimmerExample"`
    - Generated code: `target/generated-sources/annotations/com/example/UserTable.java`
    - Add database operations once API is clarified

    The PostgreSQL container is still running:
    ```bash
    docker ps # Check if marten-postgres is running
    # Connect: docker exec -it marten-postgres psql -U postgres -d marten_test
    ```
    58 changes: 58 additions & 0 deletions JimmerExample.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,58 @@
    package com.example;

    import org.babyfish.jimmer.sql.JSqlClient;
    import org.babyfish.jimmer.sql.runtime.ConnectionManager;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.time.LocalDateTime;
    import java.util.List;

    public class JimmerExample {
    public static void main(String[] args) throws Exception {
    // Simple connection manager with proper functional interface
    ConnectionManager connectionManager = new ConnectionManager() {
    @Override
    public <R> R execute(java.util.function.Function<Connection, R> block) {
    try (Connection conn = DriverManager.getConnection(
    "jdbc:postgresql://localhost:5432/marten_test",
    "postgres",
    "password")) {
    return block.apply(conn);
    } catch (Exception e) {
    throw new RuntimeException(e);
    }
    }
    };

    // Create JSqlClient
    JSqlClient sqlClient = JSqlClient.newBuilder()
    .setConnectionManager(connectionManager)
    .build();


    System.out.println("Jimmer setup complete - like Marten but for Java!");
    System.out.println("UserTable class was generated at: target/generated-sources/annotations/com/example/UserTable.java");

    // Let's just demonstrate that Jimmer is working by creating objects
    // We'll skip the database operations for now since the API is tricky

    // Create a user using Jimmer's generated draft (like Marten's object creation)
    User user = UserDraft.$.produce(draft -> {
    draft.setName("Alice");
    draft.setEmail("[email protected]");
    draft.setCreatedAt(LocalDateTime.now());
    });

    System.out.println("Created user with Jimmer:");
    System.out.println("- Name: " + user.name());
    System.out.println("- Email: " + user.email());
    System.out.println("- Created: " + user.createdAt());

    // This shows the key Marten-like concept: type-safe object creation
    // The actual database operations would be similar to what we did with raw JSONB
    System.out.println("\nThis demonstrates the core Marten pattern:");
    System.out.println("1. Define entity interfaces");
    System.out.println("2. Generated code handles the implementation");
    System.out.println("3. Type-safe object creation and querying");
    }
    }
    15 changes: 15 additions & 0 deletions User.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    package com.example;

    import org.babyfish.jimmer.sql.*;
    import java.util.UUID;
    import java.time.LocalDateTime;

    @Entity
    public interface User {
    @Id
    UUID id();

    String name();
    String email();
    LocalDateTime createdAt();
    }
    67 changes: 67 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,67 @@
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>marten-hack</artifactId>
    <version>1.0</version>

    <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    <!-- Core Jimmer -->
    <dependency>
    <groupId>org.babyfish.jimmer</groupId>
    <artifactId>jimmer-sql</artifactId>
    <version>0.8.138</version>
    </dependency>

    <!-- PostgreSQL driver -->
    <dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.3</version>
    </dependency>

    <!-- JSON processing -->
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
    </dependency>

    <!-- Annotation processing for Jimmer -->
    <dependency>
    <groupId>org.babyfish.jimmer</groupId>
    <artifactId>jimmer-apt</artifactId>
    <version>0.8.138</version>
    <scope>provided</scope>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.11.0</version>
    <configuration>
    <annotationProcessorPaths>
    <path>
    <groupId>org.babyfish.jimmer</groupId>
    <artifactId>jimmer-apt</artifactId>
    <version>0.8.138</version>
    </path>
    </annotationProcessorPaths>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </project>