-
Notifications
You must be signed in to change notification settings - Fork 0
Best Practices
Frody edited this page Sep 3, 2026
·
2 revisions
// ✅ Correct
try (Scope scope = scopeFlow.open("operation", values)) {
doWork();
}
// ❌ Wrong — scope may leak if exception occurs
Scope scope = scopeFlow.open("operation", values);
doWork();
scope.close();// ✅ Good: describes the operation
scopeFlow.open("http.request", ...)
scopeFlow.open("order.create", ...)
scopeFlow.open("payment.process", ...)
scopeFlow.open("db.query", ...)
// ❌ Bad: generic or unclear
scopeFlow.open("scope1", ...)
scopeFlow.open("work", ...)
scopeFlow.open("s", ...)// ✅ Production: explicit control over what goes to MDC/logs
MdcKeyPolicy.of(Set.of("request.id", "tenant.id", "user.id"));
// ⚠️ Development only: convenient but may leak sensitive data
MdcKeyPolicy.allowAll();// ✅ Wrap once at configuration time
@Bean
public ExecutorService myExecutor(ScopeFlow scopeFlow) {
return scopeFlow.wrapExecutorService(
Executors.newVirtualThreadPerTaskExecutor());
}
// ❌ Don't wrap individual tasks if you can wrap the executor
executor.submit(scopeFlow.wrap(() -> doWork())); // ← more verbose// ✅ Scope matches the logical operation
try (Scope scope = scopeFlow.open("db.query",
Map.of("query.table", "orders"))) {
return jdbcTemplate.query("SELECT ...");
}
// ❌ Don't keep scopes open across long waits or user interactions
try (Scope scope = scopeFlow.open("session")) {
waitForUserInput(); // Minutes/hours — scope too long
}// ✅ Block sensitive keys from appearing in context/MDC
ScopeFlowBuilder.create()
.keyPolicy(ContextKeyPolicy.denyList(Set.of(
"password", "token", "secret", "credit_card", "ssn")))
.build();// ❌ NEVER do this
try (Scope scope = scopeFlow.open("request")) {
executor.submit(() -> {
scope.put("key", "value"); // RACE CONDITION!
});
}
// ✅ Use wrap() for cross-thread propagation
executor.submit(scopeFlow.wrap(() -> {
// Context is properly captured and restored
}));// ❌ Too many keys — MDC, OTel, and logging overhead
try (Scope scope = scopeFlow.open("request",
Map.of("k1", "v1", "k2", "v2", ... "k100", "v100"))) { }
// ✅ Keep to essential correlation data
try (Scope scope = scopeFlow.open("request",
Map.of("request.id", id, "tenant.id", tenant))) { }// ❌ Context values are captured in snapshots (serialized/cloned)
scope.put("request.body", hugeJsonString); // Memory waste
// ✅ Store identifiers, not data
scope.put("request.id", requestId);// ❌ If scope2 fails to close, scope1 state is corrupted
Scope scope1 = scopeFlow.open("outer");
Scope scope2 = scopeFlow.open("inner");
// ... if exception here, scopes leak
scope2.close();
scope1.close();
// ✅ try-with-resources guarantees correct LIFO close order
try (Scope scope1 = scopeFlow.open("outer")) {
try (Scope scope2 = scopeFlow.open("inner")) {
// ...
} // scope2 always closes first
} // scope1 always closes secondspring.threads.virtual.enabled=trueScopeFlow's ThreadLocal-based isolation is designed for virtual threads with no synchronization overhead.
Each propagator's lifecycle hooks run on every scope open/close. Keep the propagator count minimal:
- MDC (if you need logs) ← almost always
- OTel (if you need distributed tracing) ← when using OTel
- Custom propagators ← only when truly needed
// Fewer keys = fewer MDC.put/MDC.remove calls = faster
MdcKeyPolicy.of(Set.of("request.id", "tenant.id"));
OtelBaggagePropagator.create(Set.of("request.id"));// ❌ Opening/closing scope per iteration is expensive
for (Item item : items) {
try (Scope scope = scopeFlow.open("process", Map.of("item.id", item.id()))) {
process(item);
}
}
// ✅ Open scope once, enrich as needed
try (Scope scope = scopeFlow.open("batch.process")) {
for (Item item : items) {
scope.put("item.id", item.id());
process(item);
}
}Always prevent sensitive data from reaching MDC/logs:
ContextKeyPolicy.denyList(Set.of("password", "token", "secret", "api_key"));Don't use MdcKeyPolicy.allowAll() in production. Explicitly list keys:
# application-prod.properties
scopeflow.mdc.keys=request.id,tenant.id,user.idUse propagators to validate tenant isolation:
public class TenantIsolationPropagator implements Propagator {
@Override
public void onScopeOpened(String name, ScopeContext ctx) {
ctx.get("tenant.id").ifPresent(tenant -> {
if (!SecurityContext.getCurrentTenant().equals(tenant)) {
throw new SecurityException("Tenant mismatch!");
}
});
}
}@BeforeEach
void setUp() {
scopeFlow = ScopeFlowBuilder.create()
.propagator(new MdcPropagator(MdcKeyPolicy.allowAll()))
.build();
}
@AfterEach
void tearDown() {
MDC.clear(); // Always clean up MDC in tests
}
@Test
void testContextPropagation() {
try (Scope scope = scopeFlow.open("test",
Map.of("request.id", "test-123"))) {
assertThat(MDC.get("request.id")).isEqualTo("test-123");
}
assertThat(MDC.get("request.id")).isNull();
}@SpringBootTest
class OrderIntegrationTest {
@Autowired
ScopeFlow scopeFlow;
@Test
void contextPropagatesInSpring() {
try (Scope scope = scopeFlow.open("test",
Map.of("request.id", "integration-test"))) {
assertThat(scopeFlow.currentContext().get("request.id"))
.hasValue("integration-test");
}
}
}ScopeFlow • Distributed Context & Tracing Propagation • Licensed under Apache-2.0