Skip to content

Commit 4f5dbf7

Browse files
committed
docs: historical inmutability
1 parent cc9a8b2 commit 4f5dbf7

File tree

2 files changed

+181
-2
lines changed

2 files changed

+181
-2
lines changed

docs/changes/best-practices.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,25 @@ public void addEmailIndexForFasterLookups(MongoDatabase db) { }
207207
public void removeEmailIndexAndRevertSchema(MongoDatabase db) { }
208208
```
209209

210-
### Avoid domain objects
210+
### Avoid domain object coupling
211211

212-
Don't use domain objects in Changes. Since Changes are immutable and your domain evolves, using domain classes can cause compilation errors when fields are removed or modified in newer versions. Instead, work with primitive types, collections, or framework-native objects like `Document` for MongoDB.
212+
Changes are historical records that must remain stable over time, even as your application evolves. When Changes depend on domain classes that later change (fields removed, renamed, or restructured), your previously successful Changes can break compilation or execution.
213+
214+
**The issue:** If a Change uses a `Customer` domain class and you later remove the `middleName` field from that class, the Change will no longer compile - breaking Flamingock's ability to verify or re-execute historical changes.
215+
216+
**✅ Use generic structures instead:**
217+
```java
218+
// Instead of domain objects, use framework-native structures
219+
@Apply
220+
public void apply(JdbcTemplate jdbc) {
221+
Map<String, Object> customer = jdbc.queryForMap(
222+
"SELECT * FROM customers WHERE id = ?", customerId
223+
);
224+
// Work with the Map directly, not a Customer object
225+
}
226+
```
227+
228+
**Learn more:** [Domain Coupling and Historical Immutability](domain-coupling.md) - Understand why this happens and explore different approaches to keep your Changes stable.
213229

214230

215231
## Naming and organization

docs/changes/domain-coupling.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
title: Domain Coupling
3+
sidebar_position: 5
4+
---
5+
6+
# Domain Coupling and Historical Immutability
7+
8+
## Why this matters
9+
10+
Here's something that might surprise you: Changes that ran successfully in the past can break your build today. This happens when Changes depend on domain classes that evolve over time. Let's understand why this matters and how to keep your Changes stable.
11+
12+
## The coupling problem
13+
14+
Changes in Flamingock are meant to be **historically immutable** - they represent past changes that have been applied and audited. Their code should remain untouched over time to ensure:
15+
16+
- **Repeatability**: The same Change produces the same result
17+
- **Auditability**: Historical changes can be verified
18+
- **Reliability**: Past Changes continue to work in new environments
19+
20+
However, when a Change depends on a domain class and that class evolves (fields removed, renamed, or restructured), your older Changes will no longer compile or run correctly.
21+
22+
### A practical example
23+
24+
Consider a PostgreSQL database with a `customers` table. Initially, your domain model includes:
25+
26+
```java
27+
public class Customer {
28+
private Long id;
29+
private String firstName;
30+
private String middleName; // Will be removed later
31+
private String lastName;
32+
private String email;
33+
// getters/setters...
34+
}
35+
```
36+
37+
You create a Change that uses this domain class:
38+
39+
```java
40+
@Change(id = "add-premium-customers", order = "0001", author = "team")
41+
public class _0001_AddPremiumCustomers {
42+
43+
@Apply
44+
public void apply(CustomerRepository repository) {
45+
Customer customer = new Customer();
46+
customer.setFirstName("John");
47+
customer.setMiddleName("William"); // Uses the field
48+
customer.setLastName("Smith");
49+
customer.setEmail("[email protected]");
50+
repository.save(customer);
51+
}
52+
}
53+
```
54+
55+
Six months later, your team decides `middleName` is unnecessary and removes it from the `Customer` class. Now:
56+
57+
- ✅ Your application works fine with the updated model
58+
- ❌ The Change `_0001_AddPremiumCustomers` no longer compiles
59+
- ❌ You can't run Flamingock in new environments
60+
- ❌ CI/CD pipelines break
61+
62+
This breaks the principle of historical immutability and undermines Flamingock's reliability.
63+
64+
## The solution: Generic structures
65+
66+
To ensure stability, avoid injecting domain classes or anything tightly coupled to your evolving business model. Instead, use schema-free or generic structures.
67+
68+
Here's how the same Change looks using generic structures:
69+
70+
```java
71+
@Change(id = "add-premium-customers", order = "0001", author = "team")
72+
public class _0001_AddPremiumCustomers {
73+
74+
@Apply
75+
public void apply(RestTemplate restTemplate) {
76+
// Using a Map instead of the Customer domain class
77+
Map<String, Object> customerData = new HashMap<>();
78+
customerData.put("firstName", "John");
79+
customerData.put("middleName", "William");
80+
customerData.put("lastName", "Smith");
81+
customerData.put("email", "[email protected]");
82+
customerData.put("status", "PREMIUM");
83+
84+
// Send to customer service API
85+
restTemplate.postForObject(
86+
"/api/customers",
87+
customerData,
88+
Map.class
89+
);
90+
}
91+
92+
@Rollback
93+
public void rollback(RestTemplate restTemplate) {
94+
// Remove the customer using email as identifier
95+
restTemplate.delete("/api/customers/[email protected]");
96+
}
97+
}
98+
```
99+
100+
This Change remains stable even if the `Customer` domain class evolves or the `middleName` field is removed. The Map structure is decoupled from your domain model.
101+
102+
## When you need reusable logic
103+
104+
If you have complex logic that needs to be shared across Changes, consider these approaches:
105+
106+
### Utility classes for Changes
107+
108+
Create utilities specifically for your Changes that are isolated from your domain:
109+
110+
```java
111+
public class ChangeUtils {
112+
public static Map<String, Object> createCustomerData(
113+
String firstName, String lastName, String email) {
114+
return Map.of(
115+
"firstName", firstName,
116+
"lastName", lastName,
117+
"email", email,
118+
"createdAt", Instant.now().toString()
119+
);
120+
}
121+
}
122+
```
123+
124+
### SQL files or scripts
125+
126+
For complex SQL operations, consider external scripts:
127+
128+
```java
129+
@Apply
130+
public void apply(JdbcTemplate jdbc) throws IOException {
131+
String sql = Files.readString(
132+
Paths.get("changes/sql/001_create_premium_customers.sql")
133+
);
134+
jdbc.execute(sql);
135+
}
136+
```
137+
138+
## Best practices summary
139+
140+
1. **Treat Changes as historical artifacts** - They are versioned records of the past, not part of your live business logic
141+
142+
2. **Use generic structures** - Maps, Documents, ResultSets, or direct queries instead of domain objects
143+
144+
3. **Keep Changes self-contained** - Minimize dependencies on external classes that might change
145+
146+
4. **Test with evolution in mind** - Ensure your Changes compile and run even as your domain evolves
147+
148+
5. **Document data structures** - When using generic structures, add comments explaining the expected schema
149+
150+
## The balance
151+
152+
We're not suggesting you should never use any classes in your Changes. The key is understanding the trade-off:
153+
154+
- **Domain classes**: Type safety now, brittleness over time
155+
- **Generic structures**: Less type safety, long-term stability
156+
157+
Choose based on your context, but be aware of the implications. For most production systems where Changes need to remain stable for years, generic structures are the safer choice.
158+
159+
## Next steps
160+
161+
- Review existing Changes for domain coupling
162+
- Establish team conventions for Change implementations
163+
- Consider using [Templates](../templates/templates-introduction.md) for standardized, decoupled change patterns

0 commit comments

Comments
 (0)