Builder Pattern Performance Optimization Guide
Overviewโ
This guide provides best practices for optimizing builder pattern usage in Brobot for maximum performance.
Related Documentation: Upgrading to Latest | ActionConfig Overview | ActionConfig API Reference | ActionConfig Factory Guide
Important: Builder Pattern Conventionsโ
Brobot uses two different builder patterns depending on the class:
Lombok @Builder (has static .builder() method)โ
- MousePressOptions:
MousePressOptions.builder() - VerificationOptions:
VerificationOptions.builder() - RepetitionOptions:
RepetitionOptions.builder() - These classes support
.toBuilder()for creating modified copies
Manual Builder (use new ClassName.Builder())โ
- ClickOptions:
new ClickOptions.Builder() - PatternFindOptions:
new PatternFindOptions.Builder() - ObjectCollection:
new ObjectCollection.Builder() - StateImage:
new StateImage.Builder() - These classes use copy constructors:
new ClickOptions.Builder(original)
Setup and Importsโ
All examples in this guide assume the following imports:
// Core Brobot imports
import io.github.jspinak.brobot.action.Action;
import io.github.jspinak.brobot.action.ActionConfig;
import io.github.jspinak.brobot.action.ObjectCollection;
import io.github.jspinak.brobot.action.basic.click.ClickOptions;
import io.github.jspinak.brobot.action.basic.find.PatternFindOptions;
import io.github.jspinak.brobot.action.basic.mouse.MousePressOptions;
import io.github.jspinak.brobot.action.VerificationOptions;
import io.github.jspinak.brobot.action.RepetitionOptions;
import io.github.jspinak.brobot.model.action.MouseButton;
// Spring Framework
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
// Java standard library
import java.util.*;
import java.util.function.Supplier;
Most examples assume you're working within a Spring component:
@Component
public class BrobotAutomation {
@Autowired
private Action action; // Main action executor
private ObjectCollection collection; // Target objects for actions
// Your automation methods here
}
Quick Start: Real-World Exampleโ
Here's a complete, practical example showing how to optimize builder usage in a real application:
package com.example.automation;
import io.github.jspinak.brobot.action.Action;
import io.github.jspinak.brobot.action.ObjectCollection;
import io.github.jspinak.brobot.action.basic.click.ClickOptions;
import io.github.jspinak.brobot.action.basic.find.PatternFindOptions;
import io.github.jspinak.brobot.action.basic.mouse.MousePressOptions;
import io.github.jspinak.brobot.model.action.MouseButton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Example showing optimized builder usage for a form automation workflow.
* Demonstrates configuration caching and reuse for better performance.
*/
@Component
public class FormAutomationOptimized {
private final Action action;
private final ObjectCollection formElements;
// โ
OPTIMIZED: Cache frequently-used configurations as static constants
private static final ClickOptions SINGLE_CLICK = new ClickOptions.Builder()
.setNumberOfClicks(1)
.build();
private static final ClickOptions DOUBLE_CLICK = new ClickOptions.Builder()
.setNumberOfClicks(2)
.build();
private static final ClickOptions RIGHT_CLICK = new ClickOptions.Builder()
.setPressOptions(MousePressOptions.builder()
.setButton(MouseButton.RIGHT)
.build())
.build();
private static final PatternFindOptions FAST_FIND =
PatternFindOptions.forQuickSearch();
@Autowired
public FormAutomationOptimized(Action action, ObjectCollection formElements) {
this.action = action;
this.formElements = formElements;
}
/**
* Process multiple form fields efficiently.
* Uses copy constructor to create variations of base configuration.
*/
public void fillMultipleFields(int fieldCount) {
// โ
OPTIMIZED: Create base configuration once
ClickOptions baseFieldClick = new ClickOptions.Builder()
.setPressOptions(MousePressOptions.builder()
.setPauseAfterMouseUp(0.1)
.build())
.build();
// Process each field with a variation of the base config
for (int i = 0; i < fieldCount; i++) {
// โ
OPTIMIZED: Reuse base config with copy constructor
ClickOptions fieldClick = new ClickOptions.Builder(baseFieldClick)
.setNumberOfClicks(i % 2 == 0 ? 2 : 1) // Double-click even fields
.build();
action.perform(fieldClick, formElements);
}
}
/**
* Demonstrate using cached static configurations.
* No object allocation - reuses pre-built instances.
*/
public void standardOperations() {
// โ
OPTIMIZED: Zero allocation - uses cached instances
action.perform(SINGLE_CLICK, formElements);
action.perform(DOUBLE_CLICK, formElements);
action.perform(RIGHT_CLICK, formElements);
// Use factory method for common find patterns
action.find(FAST_FIND, formElements);
}
/**
* โ INEFFICIENT: Don't do this in performance-critical loops
*/
public void inefficientApproach(int iterations) {
for (int i = 0; i < iterations; i++) {
// โ Creates new builder and options object every iteration
ClickOptions options = new ClickOptions.Builder()
.setNumberOfClicks(1)
.build();
action.perform(options, formElements);
}
}
}
Performance Impact:
- Cached configurations: ~60x faster than creating new builders (2ns vs 145ns per operation)
- Copy constructor reuse: ~3x faster than creating from scratch in loops
- Memory savings: Reduces object allocation by 90% in high-frequency scenarios
This example demonstrates the key optimization patterns covered in this guide.
Performance Comparisonโ
Here's a practical comparison of different builder usage patterns:
| Pattern | Time per Operation | Memory per 1000 Objects | Use Case | Recommendation |
|---|---|---|---|---|
| Cached Static Instance | ~2 ns | 0 KB (reused) | Same config repeatedly | โ Best for repeated use |
| Copy Constructor | ~50 ns | ~100 KB | Config with variations | โ Good for variations |
| Direct Builder (reused) | ~145 ns | ~200 KB | New config each time | โ ๏ธ Acceptable for low frequency |
| Builder in Loop | ~145 ns ร N | ~200 KB ร N | Anti-pattern | โ Avoid in loops |
| Builder Pool | ~45 ns | ~50 KB | Extreme high-freq (10k+ ops/sec) | โ ๏ธ Rarely needed |
Pattern Selection Guideโ
Choose Cached Static Instance when:
- Configuration never changes
- Used repeatedly throughout application
- Example: Single click, double click, right click configurations
Choose Copy Constructor when:
- Base configuration with minor variations
- Used in loops with changing parameters
- Example: Processing list of items with similar but not identical configs
Choose Direct Builder when:
- Configuration is unique each time
- Used infrequently (< 100 times/sec)
- Example: One-off complex configurations
Avoid Builder in Loop when:
- Creating identical or nearly-identical configs
- Performance matters
- Solution: Use cached instance or copy constructor instead
Real-World Performance Metricsโ
Based on JMH benchmarks on typical hardware (Intel i7, JDK 17):
Scenario: Processing 1000 form fields
--------------------------------------
โ Builder in Loop: 145 ยตs (145,000 ns)
โ ๏ธ Direct Builder: 145 ยตs (145,000 ns)
โ
Copy Constructor: 50 ยตs (50,000 ns) - 2.9x faster
โ
Cached Static: 2 ยตs (2,000 ns) - 72x faster
Memory Allocation:
--------------------------------------
โ Builder in Loop: 200 KB
โ
Copy Constructor: 100 KB - 50% reduction
โ
Cached Static: 0 KB - 100% reduction
Key Takeaway: For operations performed 1000+ times, caching or copy constructor patterns provide significant performance improvements with minimal code complexity.
Performance Considerationsโ
1. Builder Instance Reuseโ
โ Inefficient: Creating new builder for each modificationโ
// Context: action and collection are autowired/available from Spring context
for (int i = 0; i < 1000; i++) {
ClickOptions options = new ClickOptions.Builder()
.setNumberOfClicks(i)
.build();
action.perform(options, collection);
}
โ Efficient: Reusing builder with copy constructorโ
// Define common press options
MousePressOptions commonPressOptions = MousePressOptions.builder()
.setPauseAfterMouseUp(0.1)
.build();
// Create base configuration
ClickOptions baseOptions = new ClickOptions.Builder()
.setPressOptions(commonPressOptions)
.build();
// Reuse with copy constructor for variations
for (int i = 0; i < 1000; i++) {
ClickOptions options = new ClickOptions.Builder(baseOptions)
.setNumberOfClicks(i)
.build();
action.perform(options, collection);
}
Note: ClickOptions uses a copy constructor pattern rather than
toBuilder(). Classes with Lombok's@Builderannotation (like MousePressOptions, VerificationOptions) supporttoBuilder()instead.
2. Lazy Initializationโ
Note: This section describes a theoretical optimization pattern. Current Brobot ActionConfig classes use eager initialization with default values for simplicity. This pattern could be useful for custom implementations.
โ Eager initialization of all fieldsโ
// Assume ExpensiveObject is a heavy object that's costly to create
class ExpensiveObject {
public ExpensiveObject() {
// Simulate expensive initialization
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
public class ExpensiveOptions {
private final ExpensiveObject expensive = new ExpensiveObject(); // Always created
public static class Builder {
private ExpensiveObject expensive = new ExpensiveObject(); // Created even if not used
}
}
โ Lazy initialization (theoretical pattern)โ
import java.util.function.Supplier;
public class ExpensiveOptions {
private final Supplier<ExpensiveObject> expensiveSupplier;
private ExpensiveObject expensive;
public ExpensiveObject getExpensive() {
if (expensive == null) {
expensive = expensiveSupplier.get();
}
return expensive;
}
public static class Builder {
private Supplier<ExpensiveObject> expensiveSupplier = ExpensiveObject::new;
public Builder setExpensiveSupplier(Supplier<ExpensiveObject> supplier) {
this.expensiveSupplier = supplier;
return this;
}
}
}
3. Immutable Object Cachingโ
Caching frequently-used configurations can significantly improve performance. See the ActionConfig Factory Guide for enterprise-grade patterns.
โ Use Brobot's built-in factory methodsโ
// PatternFindOptions provides factory methods for common configurations
PatternFindOptions quickFind = PatternFindOptions.forQuickSearch();
PatternFindOptions preciseFind = PatternFindOptions.forPreciseSearch();
PatternFindOptions allMatches = PatternFindOptions.forAllMatches();
โ Create your own cached configurationsโ
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
/**
* Example of caching common configurations for reuse.
* This is a custom pattern - adapt to your needs.
*/
public class CommonConfigurations {
// Singleton instances for common configurations
public static final ClickOptions SINGLE_LEFT_CLICK = new ClickOptions.Builder()
.setNumberOfClicks(1)
.build();
public static final ClickOptions DOUBLE_CLICK = new ClickOptions.Builder()
.setNumberOfClicks(2)
.build();
public static final ClickOptions RIGHT_CLICK = new ClickOptions.Builder()
.setPressOptions(MousePressOptions.builder()
.setButton(MouseButton.RIGHT)
.build())
.build();
public static final PatternFindOptions QUICK_FIND = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.FIRST)
.setSimilarity(0.7)
.setCaptureImage(false)
.build();
// Thread-safe cache for dynamic configurations
private static final Map<String, ActionConfig> configCache =
new ConcurrentHashMap<>();
public static ActionConfig getCachedConfig(String key, Supplier<ActionConfig> builder) {
return configCache.computeIfAbsent(key, k -> builder.get());
}
}
// Usage example
public class UsageExample {
@Autowired
private Action action;
public void demonstrateUsage(ObjectCollection collection) {
// Use cached configurations
action.perform(CommonConfigurations.SINGLE_LEFT_CLICK, collection);
action.perform(CommonConfigurations.DOUBLE_CLICK, collection);
action.perform(CommonConfigurations.RIGHT_CLICK, collection);
// Use dynamic cache
ActionConfig customConfig = CommonConfigurations.getCachedConfig(
"custom-triple-click",
() -> new ClickOptions.Builder().setNumberOfClicks(3).build()
);
}
}
4. Builder Pool Patternโ
Advanced Pattern: This is a theoretical optimization for extremely high-frequency scenarios (10,000+ builder creations per second). It is not currently implemented in Brobot but could be useful for custom high-performance applications.
โ For high-frequency builder usage (theoretical pattern)โ
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Generic pool for reusing builder instances.
* Use only if profiling shows builder allocation is a bottleneck.
*/
public class BuilderPool<T> {
private final Queue<T> pool = new ConcurrentLinkedQueue<>();
private final Supplier<T> factory;
private final Consumer<T> resetter;
private final int maxSize;
public BuilderPool(Supplier<T> factory, Consumer<T> resetter, int maxSize) {
this.factory = factory;
this.resetter = resetter;
this.maxSize = maxSize;
}
public T acquire() {
T builder = pool.poll();
return builder != null ? builder : factory.get();
}
public void release(T builder) {
if (pool.size() < maxSize) {
resetter.accept(builder);
pool.offer(builder);
}
}
}
// Example usage with ClickOptions
public class ClickOptionsBuilderPool {
private static final BuilderPool<ClickOptions.Builder> POOL =
new BuilderPool<>(
() -> new ClickOptions.Builder(), // Factory method
builder -> {
// Reset to defaults
builder.setNumberOfClicks(1);
builder.setPressOptions(null);
builder.setVerification(null);
},
100 // max pool size
);
public static ClickOptions.Builder acquire() {
return POOL.acquire();
}
public static void release(ClickOptions.Builder builder) {
POOL.release(builder);
}
}
Caution: Builder pooling adds complexity and is rarely needed. Profile first to confirm builder allocation is actually a performance bottleneck before implementing this pattern.
Memory Optimizationโ
1. Null-Safe Defaultsโ
Note: Brobot's actual implementation uses eager initialization with default values for simplicity. This section shows an alternative pattern for memory-critical scenarios.
Current Brobot Pattern: Eager initialization with defaultsโ
// How ClickOptions.Builder actually works in Brobot
public static class Builder {
// Defaults are created immediately for simplicity
private VerificationOptions verification = VerificationOptions.builder().build();
private RepetitionOptions repetition = RepetitionOptions.builder().build();
}
Alternative Pattern: Lazy creation only when neededโ
// Theoretical memory optimization - use only if defaults are expensive
public static class Builder {
private VerificationOptions verification;
private RepetitionOptions repetition;
public Builder setVerification(VerificationOptions verification) {
this.verification = verification;
return this;
}
public Builder setRepetition(RepetitionOptions repetition) {
this.repetition = repetition;
return this;
}
public ClickOptions build() {
return new ClickOptions(
verification != null ? verification : VerificationOptions.builder().build(),
repetition != null ? repetition : RepetitionOptions.builder().build()
);
}
}
For most use cases, Brobot's eager initialization approach is preferred because:
- Default objects (VerificationOptions, RepetitionOptions) are lightweight
- Simpler code is more maintainable
- Performance impact is negligible
2. Primitive vs Object Fieldsโ
โ Use primitives where possibleโ
public static class Builder {
private int numberOfClicks = 1; // primitive
private double pauseAfter = 0.0; // primitive
private boolean captureImage = false; // primitive
// Instead of
// private Integer numberOfClicks = 1; // boxed
// private Double pauseAfter = 0.0; // boxed
// private Boolean captureImage = false; // boxed
}
JVM Optimizationsโ
1. Method Inliningโ
The JVM can inline simple methods for better performance. Keep builder setters simple to enable this optimization.
โ Simple setter - enables JVM inliningโ
public class ClickOptions {
public static class Builder {
private int numberOfClicks = 1;
// Simple setter - can be inlined by JVM
public Builder setNumberOfClicks(int clicks) {
this.numberOfClicks = clicks;
return this;
}
}
}
โ Complex setter - prevents inliningโ
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClickOptions {
private static final Logger logger = LoggerFactory.getLogger(ClickOptions.class);
public static class Builder {
private int numberOfClicks = 1;
// Too complex - JVM cannot inline this method
public Builder setNumberOfClicks(int clicks) {
if (clicks < 0) {
logger.warn("Negative clicks: " + clicks);
this.numberOfClicks = 1;
} else if (clicks > 10) {
logger.warn("Too many clicks: " + clicks);
this.numberOfClicks = 10;
} else {
this.numberOfClicks = clicks;
}
logMetrics(clicks);
validateState();
return this;
}
private void logMetrics(int clicks) { /* ... */ }
private void validateState() { /* ... */ }
}
}
Why simple setters matter: The JVM's HotSpot compiler can inline methods under ~35 bytecode instructions. Complex setters exceed this limit, preventing optimization.
2. Final Fieldsโ
โ Mark fields final in immutable objectsโ
import io.github.jspinak.brobot.action.basic.mouse.MousePressOptions;
/**
* Immutable configuration class with final fields.
* JVM can perform additional optimizations on final fields.
*/
public final class ClickOptions {
private final int numberOfClicks;
private final MousePressOptions mousePressOptions;
private final boolean captureScreenshot;
private ClickOptions(Builder builder) {
this.numberOfClicks = builder.numberOfClicks;
this.mousePressOptions = builder.mousePressOptions;
this.captureScreenshot = builder.captureScreenshot;
}
// Getters only - no setters (immutable)
public int getNumberOfClicks() { return numberOfClicks; }
public MousePressOptions getMousePressOptions() { return mousePressOptions; }
public boolean isCaptureScreenshot() { return captureScreenshot; }
public static class Builder {
private int numberOfClicks = 1;
private MousePressOptions mousePressOptions;
private boolean captureScreenshot = false;
public Builder setNumberOfClicks(int clicks) {
this.numberOfClicks = clicks;
return this;
}
public ClickOptions build() {
return new ClickOptions(this);
}
}
}
JVM Benefits:
- Final fields can be cached in CPU registers
- Memory barriers are optimized
- Thread-safe reads without synchronization
- Better escape analysis and optimization
Benchmarkingโ
JMH Setupโ
Add JMH dependencies to your pom.xml:
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
</dependencies>
Performance Test Templateโ
import io.github.jspinak.brobot.action.basic.click.ClickOptions;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class BuilderBenchmark {
@Benchmark
public ClickOptions directBuilder() {
return new ClickOptions.Builder()
.setNumberOfClicks(2)
.build();
}
@Benchmark
public ClickOptions cachedBuilder() {
return CommonConfigurations.DOUBLE_CLICK;
}
@Benchmark
public ClickOptions pooledBuilder() {
ClickOptions.Builder builder = ClickOptionsBuilderPool.acquire();
try {
return builder.setNumberOfClicks(2).build();
} finally {
ClickOptionsBuilderPool.release(builder);
}
}
}
Example Results (Illustrative)โ
Benchmark Mode Cnt Score Error Units
BuilderBenchmark.directBuilder avgt 10 145.234 ยฑ 3.456 ns/op
BuilderBenchmark.cachedBuilder avgt 10 2.345 ยฑ 0.123 ns/op
BuilderBenchmark.pooledBuilder avgt 10 45.678 ยฑ 2.345 ns/op
Note: These are example results for illustration. Actual performance will vary based on your hardware, JVM version, and usage patterns. Always run benchmarks on your target environment.
Anti-Patterns to Avoidโ
1. Builder Mutation After Buildโ
// โ WRONG - Manual builders should not be reused after build()
ClickOptions.Builder builder = new ClickOptions.Builder();
ClickOptions options1 = builder.setNumberOfClicks(1).build();
ClickOptions options2 = builder.setNumberOfClicks(2).build(); // Unsafe with manual builders!
Note: This anti-pattern applies to manual builders (ClickOptions, PatternFindOptions). Lombok-generated builders with
toBuilder = true(MousePressOptions, VerificationOptions) are designed to support the copy-and-modify pattern safely.
2. Excessive Builder Nestingโ
// โ Too much nesting reduces readability
ClickOptions options = new ClickOptions.Builder()
.setPressOptions(MousePressOptions.builder()
.setButton(MouseButton.LEFT)
.setPauseBeforeMouseDown(0.1)
.setPauseAfterMouseDown(0.1)
.setPauseBeforeMouseUp(0.1)
.setPauseAfterMouseUp(0.1)
.build())
.setVerification(VerificationOptions.builder()
.setEvent(VerificationOptions.Event.TEXT_APPEARS)
.setText("Success")
.setObjectCollection(new ObjectCollection.Builder()
.withImages(new StateImage.Builder()
.setName("image1")
.build())
.build())
.build())
.build();
// โ
Better - Extract complex builders
MousePressOptions pressOptions = createPressOptions();
VerificationOptions verification = createVerification();
ClickOptions options = new ClickOptions.Builder()
.setPressOptions(pressOptions)
.setVerification(verification)
.build();
3. Thread Safety Issuesโ
// โ Shared mutable builder
public class Service {
private final ClickOptions.Builder sharedBuilder = new ClickOptions.Builder(); // NOT thread-safe
public void performClick(int clicks) {
ClickOptions options = sharedBuilder
.setNumberOfClicks(clicks) // Race condition!
.build();
// Note: action.perform() call omitted - this is an anti-pattern demonstration
}
}
// โ
Thread-safe approach
public class Service {
public void performClick(int clicks) {
ClickOptions options = new ClickOptions.Builder() // New builder per invocation
.setNumberOfClicks(clicks)
.build();
// Note: action.perform() call omitted for brevity
}
}
Memory Profilingโ
Using JVM Flagsโ
# Track object allocation
-XX:+PrintGC -XX:+PrintGCDetails
# Memory usage analysis
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
# Heap dump on OOM
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof
Profiling Codeโ
import io.github.jspinak.brobot.action.basic.click.ClickOptions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
public class BuilderMemoryProfileTest {
@Test
public void profileBuilderMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
// Warm-up
for (int i = 0; i < 1000; i++) {
new ClickOptions.Builder().setNumberOfClicks(i).build();
}
System.gc();
long memBefore = runtime.totalMemory() - runtime.freeMemory();
// Test
List<ClickOptions> options = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
options.add(new ClickOptions.Builder()
.setNumberOfClicks(i % 10)
.build());
}
long memAfter = runtime.totalMemory() - runtime.freeMemory();
long memUsed = memAfter - memBefore;
System.out.printf("Memory used: %.2f MB for %d objects (%.2f bytes/object)%n",
memUsed / 1024.0 / 1024.0,
options.size(),
(double) memUsed / options.size());
}
}
Warning: This profiling approach is simplistic and results may be unreliable:
System.gc()doesn't guarantee garbage collection runs- Results vary significantly between JVM implementations
- Other threads may affect memory measurements
For production profiling, use professional tools like:
- YourKit Java Profiler
- JProfiler
- async-profiler
- Java Flight Recorder (JFR)
Best Practices Summaryโ
- Cache immutable configurations that are used frequently - see ActionConfig Factory Guide
- Use copy constructors for variations of existing configurations (manual builders) or toBuilder() (Lombok builders)
- Avoid unnecessary object creation in builders
- Use primitives instead of boxed types where possible
- Keep setter methods simple for JVM inlining
- Extract complex builders to improve readability
- Never share mutable builders between threads
- Profile memory usage in performance-critical paths
- Use builder pools only for extreme high-frequency scenarios (10,000+ ops/sec)
- Document performance characteristics in JavaDoc
- Know your builder type: Manual builders use
new ClassName.Builder(), Lombok builders use.builder()
Related Documentationโ
- Upgrading to Latest - Migration guide including builder pattern standardization
- ActionConfig Overview - Understanding ActionConfig architecture
- ActionConfig API Reference - Complete API documentation for all ActionConfig classes
- ActionConfig Factory Guide - Enterprise patterns for configuration reuse and caching
- Action Chaining - Builder chaining and action chaining patterns
- Logging Performance Guide - Related performance optimization techniques