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/
Instead of traditional relational tables, Marten stores entire objects as JSON documents:
-- 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
);
Marten translates C# LINQ expressions into PostgreSQL JSON operators:
// 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'
->>
: Extract field as text (data->>'email'
)->
: Extract field as JSON (data->'address'
)@>
: Contains (data @> '{"name": "Alice"}'
)?
: Key exists (data ? 'email'
)
We built a simple example showing what Marten does under the hood:
// 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.
Jimmer provides similar functionality to Marten for the JVM ecosystem:
@Entity
public interface User {
@Id
UUID id();
String name();
String email();
LocalDateTime createdAt();
}
User user = UserDraft.$.produce(draft -> {
draft.setName("Alice");
draft.setEmail("[email protected]");
draft.setCreatedAt(LocalDateTime.now());
});
Like Marten, Jimmer uses code generation at compile time:
- Generates
UserDraft
for object creation - Generates
UserTable
for type-safe querying - Handles immutable object implementations
# 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"
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
- 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
- Define data shape once (class/interface)
- Generated code handles:
- Object creation/mutation
- Serialization/deserialization
- Type-safe query builders
- Database schema management
- 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
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
- Get .NET working - Either through IT approval or alternative approach
- Try real Marten examples - Document storage, event sourcing, projections
- Compare with current architecture - Understand migration implications
- Explore Marten's "warts" - Concurrency handling, performance characteristics, serialization edge cases
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:
docker ps # Check if marten-postgres is running
# Connect: docker exec -it marten-postgres psql -U postgres -d marten_test
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:
// 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:
<!-- 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:
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.