Skip to content

feat: Object Pooling for Memory Optimization#3

Open
castletrade wants to merge 11 commits into
mainfrom
feature/system-optimization
Open

feat: Object Pooling for Memory Optimization#3
castletrade wants to merge 11 commits into
mainfrom
feature/system-optimization

Conversation

@castletrade
Copy link
Copy Markdown
Owner

@castletrade castletrade commented Apr 21, 2026

This PR implements an OrderPool and refactors the Order model to support object reuse.

Why:

In high-frequency trading (HFT) environments, frequent allocation/deallocation of short-lived domain objects like Orders can lead to significant Garbage Collection (GC) pauses.

Changes:

  • Added @NoArgsConstructor and clear() method to Order domain model.
    • Implemented OrderPool using ArrayBlockingQueue for thread-safe pre-allocation.
    • Added comprehensive unit tests in OrderPoolTest.
      This closes the Memory Optimization issue.

Summary by Sourcery

Introducir un pool de objetos Order para reducir la sobrecarga de asignación y permitir la reutilización de objetos en cargas de trabajo de trading de alta frecuencia.

Nuevas funcionalidades:

  • Añadir un componente OrderPool que preasigna y gestiona instancias reutilizables de Order con un tamaño de pool configurable.
  • Exponer un método para consultar el número actual de objetos Order disponibles en el pool.

Mejoras:

  • Ampliar el modelo de dominio Order con constructores sin argumentos y con todos los argumentos, y un método clear() para restablecer el estado para su reutilización.

Tests:

  • Añadir OrderPoolTest para verificar el comportamiento de préstamo, devolución y agotamiento del pool de Order.
Original summary in English

Summary by Sourcery

Introduce an Order object pool to reduce allocation overhead and support object reuse in high-frequency trading workloads.

New Features:

  • Add an OrderPool component that pre-allocates and manages reusable Order instances with configurable pool size.
  • Expose a method to query the current number of available Order objects in the pool.

Enhancements:

  • Extend the Order domain model with no-args and all-args constructors and a clear() method to reset state for reuse.

Tests:

  • Add OrderPoolTest to verify borrowing, returning, and exhaustion behavior of the Order pool.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 21, 2026

Guía para revisores

Introduce un pool de objetos Order para reducir la presión del GC, refactoriza el modelo Order para admitir la reutilización mediante constructores y un método clear(), y añade pruebas unitarias que validan el comportamiento del pool y el manejo del agotamiento.

Diagrama de secuencia para tomar prestados y devolver Orders a través de OrderPool

sequenceDiagram
actor Client
participant OrderPool
participant Order

Client->>OrderPool: borrowOrder()
alt Order available in pool
  OrderPool->>OrderPool: poll()
  OrderPool-->>Client: pooled Order
else Pool exhausted
  OrderPool->>Order: new Order()
  OrderPool-->>Client: transient Order
end

Client->>OrderPool: returnOrder(order)
OrderPool->>Order: clear()
OrderPool->>OrderPool: offer(order)
alt Pool full
  OrderPool-->>Client: discard order
else Accepted into pool
  OrderPool-->>Client: order returned to pool
end
Loading

Diagrama de clases para Order y OrderPool con soporte de pool de objetos

classDiagram
class Order {
  String id
  String symbol
  AssetClass assetClass
  OrderSide side
  Integer quantity
  Double price
  ExecutionType executionType
  OrderStatus status
  LocalDateTime timestamp
  +Order()
  +Order(String id, String symbol, AssetClass assetClass, OrderSide side, Integer quantity, Double price, ExecutionType executionType, OrderStatus status, LocalDateTime timestamp)
  +void clear()
}

class AssetClass {
  <<enumeration>>
  EQUITY
  FX
  CRYPTO
  FIXED_INCOME
}

class OrderPool {
  -BlockingQueue~Order~ pool
  -static int DEFAULT_POOL_SIZE
  +OrderPool()
  +OrderPool(int size)
  +Order borrowOrder()
  +void returnOrder(Order order)
  +int getAvailableCount()
}

Order o-- AssetClass
OrderPool o--> Order
Loading

Cambios a nivel de archivo

Cambio Detalles Archivos
Refactorizar el modelo de dominio Order para admitir el pool de objetos y el restablecimiento de estado.
  • Añadir constructores Lombok sin argumentos y con todos los argumentos para habilitar la preasignación del pool y una instanciación flexible.
  • Introducir el método clear() que pone a null todos los campos para que las instancias de Order se puedan reutilizar de forma segura.
  • Conservar los campos existentes y las definiciones de enums, haciendo a la vez que la clase sea compatible con las semánticas de pooling.
src/main/java/com/castletrade/oms/core/domain/model/Order.java
Implementar un OrderPool seguro para hilos para preasignar y reutilizar instancias de Order.
  • Añadir el componente Spring OrderPool respaldado por un ArrayBlockingQueue con una capacidad predeterminada de 1000.
  • Pre‑poblar el pool con instancias de Order construidas por defecto durante la inicialización y registrar (log) el tamaño del pool.
  • Proporcionar borrowOrder() que devuelve un Order del pool o asigna una nueva instancia transitoria si el pool está agotado, registrando una advertencia cuando se agota.
  • Proporcionar returnOrder() que limpia el Order e intenta devolverlo al pool, descartándolo y registrándolo cuando el pool está lleno.
  • Exponer getAvailableCount() para observabilidad del número actual de objetos en el pool.
src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java
Añadir pruebas unitarias para verificar el comportamiento de OrderPool al tomar y devolver órdenes, y en situaciones de agotamiento.
  • Probar que tomar prestado reduce el recuento disponible, devolverlo lo restaura y que los Orders devueltos se limpian antes de su reutilización.
  • Probar el comportamiento de agotamiento del pool cuando la capacidad es 1, asegurando que se asigna un nuevo Order y que el reporte del tamaño del pool es correcto.
src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java

Posibles issues relacionados

  • #Memory Optimization: Este PR añade OrderPool y un modelo Order reutilizable, implementando directamente la optimización de memoria solicitada mediante el uso de un pool de objetos.

Consejos y comandos

Interacción con Sourcery

  • Lanzar una nueva revisión: Comenta @sourcery-ai review en el pull request.
  • Continuar discusiones: Responde directamente a los comentarios de revisión de Sourcery.
  • Generar un issue de GitHub a partir de un comentario de revisión: Pide a Sourcery que cree un issue a partir de un comentario de revisión respondiendo a dicho comentario. También puedes responder a un comentario de revisión con @sourcery-ai issue para crear un issue a partir de él.
  • Generar un título para el pull request: Escribe @sourcery-ai en cualquier parte del título del pull request para generar un título en cualquier momento. También puedes comentar @sourcery-ai title en el pull request para (re)generar el título en cualquier momento.
  • Generar un resumen del pull request: Escribe @sourcery-ai summary en cualquier parte del cuerpo del pull request para generar un resumen del PR en cualquier momento exactamente donde lo quieras. También puedes comentar @sourcery-ai summary en el pull request para (re)generar el resumen en cualquier momento.
  • Generar la guía para revisores: Comenta @sourcery-ai guide en el pull request para (re)generar la guía para revisores en cualquier momento.
  • Resolver todos los comentarios de Sourcery: Comenta @sourcery-ai resolve en el pull request para resolver todos los comentarios de Sourcery. Útil si ya has abordado todos los comentarios y no quieres verlos más.
  • Descartar todas las revisiones de Sourcery: Comenta @sourcery-ai dismiss en el pull request para descartar todas las revisiones de Sourcery existentes. Especialmente útil si quieres empezar de cero con una nueva revisión; no olvides comentar @sourcery-ai review para lanzar una nueva revisión.

Personalizar tu experiencia

Accede a tu dashboard para:

  • Activar o desactivar funcionalidades de revisión como el resumen del pull request generado por Sourcery, la guía para revisores y otras.
  • Cambiar el idioma de la revisión.
  • Añadir, eliminar o editar instrucciones de revisión personalizadas.
  • Ajustar otros parámetros de revisión.

Obtener ayuda

Original review guide in English

Reviewer's Guide

Introduces an Order object pool to reduce GC pressure, refactors the Order model to support reuse via constructors and a clear() method, and adds unit tests validating pool behavior and exhaustion handling.

Sequence diagram for borrowing and returning Orders via OrderPool

sequenceDiagram
actor Client
participant OrderPool
participant Order

Client->>OrderPool: borrowOrder()
alt Order available in pool
  OrderPool->>OrderPool: poll()
  OrderPool-->>Client: pooled Order
else Pool exhausted
  OrderPool->>Order: new Order()
  OrderPool-->>Client: transient Order
end

Client->>OrderPool: returnOrder(order)
OrderPool->>Order: clear()
OrderPool->>OrderPool: offer(order)
alt Pool full
  OrderPool-->>Client: discard order
else Accepted into pool
  OrderPool-->>Client: order returned to pool
end
Loading

Class diagram for Order and OrderPool with object pooling support

classDiagram
class Order {
  String id
  String symbol
  AssetClass assetClass
  OrderSide side
  Integer quantity
  Double price
  ExecutionType executionType
  OrderStatus status
  LocalDateTime timestamp
  +Order()
  +Order(String id, String symbol, AssetClass assetClass, OrderSide side, Integer quantity, Double price, ExecutionType executionType, OrderStatus status, LocalDateTime timestamp)
  +void clear()
}

class AssetClass {
  <<enumeration>>
  EQUITY
  FX
  CRYPTO
  FIXED_INCOME
}

class OrderPool {
  -BlockingQueue~Order~ pool
  -static int DEFAULT_POOL_SIZE
  +OrderPool()
  +OrderPool(int size)
  +Order borrowOrder()
  +void returnOrder(Order order)
  +int getAvailableCount()
}

Order o-- AssetClass
OrderPool o--> Order
Loading

File-Level Changes

Change Details Files
Refactor Order domain model to support object pooling and state reset.
  • Add Lombok no-args and all-args constructors to enable pool preallocation and flexible instantiation.
  • Introduce clear() method that nulls all fields so Order instances can be safely reused.
  • Retain existing fields and enum definitions while making the class compatible with pooling semantics.
src/main/java/com/castletrade/oms/core/domain/model/Order.java
Implement a thread-safe OrderPool for pre-allocating and reusing Order instances.
  • Add OrderPool Spring component backed by an ArrayBlockingQueue with a default capacity of 1000.
  • Pre-populate the pool with default-constructed Order instances on initialization and log pool size.
  • Provide borrowOrder() that returns an Order from the pool or allocates a new transient instance if exhausted, logging a warning on exhaustion.
  • Provide returnOrder() that clears the Order and attempts to put it back into the pool, discarding and logging when the pool is full.
  • Expose getAvailableCount() for observability into the current number of pooled objects.
src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java
Add unit tests to verify OrderPool borrowing, returning, and exhaustion behavior.
  • Test that borrowing decreases available count, returning restores it, and that returned Orders are cleared before reuse.
  • Test pool exhaustion behavior when capacity is 1, ensuring a new Order is allocated and pool size reporting is correct.
src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java

Possibly linked issues

  • #Memory Optimization: PR adds OrderPool and reusable Order model, directly implementing the requested memory optimization via object pooling.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - he encontrado 7 problemas y he dejado algunos comentarios de alto nivel:

  • Actualmente el tamaño del pool está codificado (y el @component solo usa el constructor sin argumentos); plantéate hacer que la capacidad del pool sea configurable externamente (por ejemplo, mediante propiedades de la aplicación / inyección por constructor) para poder ajustarla por despliegue.
  • Dado que las instancias de Order ahora son mutables y se reutilizan entre hilos a través del pool, sería útil documentar explícitamente en el Javadoc de OrderPool que los consumidores no deben retener referencias ni usar un Order después de devolverlo al pool para evitar errores de concurrencia sutiles.
  • El log de advertencia cuando se agota el pool puede volverse muy ruidoso bajo carga sostenida; considera reducir el nivel de log, aplicar limitación de frecuencia (rate limiting) o registrar el estado de agotamiento solo cuando se cruce un umbral.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The pool size is currently hardcoded (and the @Component uses only the no-arg constructor); consider making the pool capacity externally configurable (e.g., via application properties / constructor injection) so it can be tuned per deployment.
- Since Order instances are now mutable and reused across threads via the pool, it would be helpful to explicitly document in the OrderPool Javadoc that callers must not retain references or use an Order after returning it to the pool to avoid subtle concurrency bugs.
- The warning log on pool exhaustion may become very noisy under sustained load; consider either reducing the log level, rate limiting, or logging the exhaustion state only when a threshold is crossed.

## Individual Comments

### Comment 1
<location path="src/main/java/com/castletrade/oms/core/domain/model/Order.java" line_range="29-38" />
<code_context>
+    /**
+     * Resets the order state for reuse in an object pool.
+     */
+    public void clear() {
+        this.id = null;
+        this.symbol = null;
+        this.assetClass = null;
+        this.side = null;
+        this.quantity = null;
+        this.price = null;
+        this.executionType = null;
+        this.status = null;
+        this.timestamp = null;
+    }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider restricting `clear()` visibility to limit misuse outside pooling contexts.

As a public method, `clear()` lets any caller reset an `Order` that may still be in use, which can cause subtle bugs (e.g., entries in collections suddenly losing their identity fields). Since it exists for pooling, consider making it package‑private or otherwise limiting visibility so only `OrderPool` can call it.

Suggested implementation:

```java
    /**
     * Resets the order state for reuse in an object pool.
     * Package-private to prevent misuse outside pooling contexts.
     */
    void clear() {
        this.id = null;
        this.symbol = null;
        this.assetClass = null;
        this.side = null;
        this.quantity = null;
        this.price = null;

```

1. Ensure any code (e.g., `OrderPool`) that invokes `clear()` resides in the same package `com.castletrade.oms.core.domain.model` or adjust its package accordingly.
2. If there are tests calling `order.clear()` from a different package, either move those tests into the same package or access it via the pooling abstraction instead of directly calling `clear()`.
</issue_to_address>

### Comment 2
<location path="src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java" line_range="31-39" />
<code_context>
+    /**
+     * Borrows an order from the pool.
+     */
+    public Order borrowOrder() {
+        Order order = pool.poll();
+        if (order == null) {
+            log.warn("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
+            return new Order();
+        }
</code_context>
<issue_to_address>
**suggestion (performance):** Using WARN for pool exhaustion in a hot path may generate excessive log noise under load.

In high-throughput scenarios, this condition may be hit frequently, so `WARN` here can both slow the hot path and flood logs. Consider lowering the level to `INFO`/`DEBUG` or adding throttling (e.g., log every Nth occurrence or on a time interval) to preserve observability without excessive log I/O.

```suggestion
    /**
     * Borrows an order from the pool.
     */
    public Order borrowOrder() {
        Order order = pool.poll();
        if (order == null) {
            log.debug("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
            return new Order();
        }
```
</issue_to_address>

### Comment 3
<location path="src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java" line_range="46-51" />
<code_context>
+    /**
+     * Returns an order to the pool after resetting its state.
+     */
+    public void returnOrder(Order order) {
+        if (order == null) return;
+        order.clear();
+        boolean accepted = pool.offer(order);
+        if (!accepted) {
+            log.debug("OrderPool full, discarding order object.");
+        }
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Returning the same `Order` instance multiple times can lead to multiple references to the same object in the pool.

Since `ArrayBlockingQueue` allows duplicates, the same `Order` can be added to the pool multiple times if `returnOrder` is called twice on it (or on an object never borrowed). That can lead to the same mutable instance being handed to multiple borrowers concurrently. If this is possible in your usage, add a guard (e.g., an `inPool` flag on `Order` or a small tracking structure) to prevent double returns or foreign objects being returned.
</issue_to_address>

### Comment 4
<location path="src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java" line_range="9-18" />
<code_context>
+class OrderPoolTest {
+
+    @Test
+    void testBorrowAndReturn() {
+        OrderPool pool = new OrderPool(10);
+        assertEquals(10, pool.getAvailableCount());
+
+        Order order = pool.borrowOrder();
+        assertNotNull(order);
+        assertEquals(9, pool.getAvailableCount());
+
+        order.setSymbol("BTC/USD");
+        pool.returnOrder(order);
+
+        assertEquals(10, pool.getAvailableCount());
+        
+        Order reborrowed = pool.borrowOrder();
+        assertNull(reborrowed.getSymbol(), "Order should be cleared when returned to pool");
+    }
+
</code_context>
<issue_to_address>
**suggestion (testing):** Extend the borrow/return test to verify all Order fields are cleared, not just symbol.

Since `clear()` resets all fields (`id`, `assetClass`, `side`, `quantity`, `price`, `executionType`, `status`, `timestamp`), initialize several of these before `returnOrder` and assert they are all cleared on re-borrow. This strengthens the test to catch future regressions where a field is accidentally not reset.

Suggested implementation:

```java
    @Test
    void testBorrowAndReturn() {
        OrderPool pool = new OrderPool(10);
        assertEquals(10, pool.getAvailableCount());

        Order order = pool.borrowOrder();
        assertNotNull(order);
        assertEquals(9, pool.getAvailableCount());

        // initialize multiple fields so we can verify they are cleared
        order.setId(123L);
        order.setSymbol("BTC/USD");
        order.setAssetClass(AssetClass.CRYPTO);
        order.setSide(Side.BUY);
        order.setQuantity(new java.math.BigDecimal("1.23"));
        order.setPrice(new java.math.BigDecimal("45678.90"));
        order.setExecutionType(ExecutionType.MARKET);
        order.setStatus(OrderStatus.NEW);
        order.setTimestamp(java.time.Instant.now());

        pool.returnOrder(order);

        assertEquals(10, pool.getAvailableCount());

        Order reborrowed = pool.borrowOrder();

        // all fields should be cleared when returned to pool
        assertNull(reborrowed.getId(), "Order id should be cleared when returned to pool");
        assertNull(reborrowed.getSymbol(), "Order symbol should be cleared when returned to pool");
        assertNull(reborrowed.getAssetClass(), "Order assetClass should be cleared when returned to pool");
        assertNull(reborrowed.getSide(), "Order side should be cleared when returned to pool");
        assertNull(reborrowed.getQuantity(), "Order quantity should be cleared when returned to pool");
        assertNull(reborrowed.getPrice(), "Order price should be cleared when returned to pool");
        assertNull(reborrowed.getExecutionType(), "Order executionType should be cleared when returned to pool");
        assertNull(reborrowed.getStatus(), "Order status should be cleared when returned to pool");
        assertNull(reborrowed.getTimestamp(), "Order timestamp should be cleared when returned to pool");
    }

```

- Ensure the `Order` class exposes getters/setters matching the method names used above: `getId`, `setId`, `getAssetClass`, `setAssetClass`, `getSide`, `setSide`, `getQuantity`, `setQuantity`, `getPrice`, `setPrice`, `getExecutionType`, `setExecutionType`, `getStatus`, `setStatus`, `getTimestamp`, `setTimestamp`.
- Adjust enum/type names if your domain model uses different ones (e.g. `OrderAssetClass` instead of `AssetClass`, `OrderSide` instead of `Side`, `ExecutionType`/`OrderExecutionType`, `OrderStatus` instead of `OrderStatus`, and `Instant` vs another timestamp type).
- If any of these fields are primitives rather than nullable reference types, change the corresponding assertions to check for the appropriate reset default (e.g. `assertEquals(0L, reborrowed.getId())`) instead of `assertNull`.
- You may also want to add static imports for `BigDecimal` and `Instant` (or your timestamp type) instead of using fully-qualified names if that better matches your test code style.
</issue_to_address>

### Comment 5
<location path="src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java" line_range="8-17" />
<code_context>
+
+class OrderPoolTest {
+
+    @Test
+    void testBorrowAndReturn() {
+        OrderPool pool = new OrderPool(10);
+        assertEquals(10, pool.getAvailableCount());
+
+        Order order = pool.borrowOrder();
+        assertNotNull(order);
+        assertEquals(9, pool.getAvailableCount());
+
+        order.setSymbol("BTC/USD");
+        pool.returnOrder(order);
+
+        assertEquals(10, pool.getAvailableCount());
+        
+        Order reborrowed = pool.borrowOrder();
+        assertNull(reborrowed.getSymbol(), "Order should be cleared when returned to pool");
+    }
+
+    @Test
+    void testPoolExhaustion() {
+        OrderPool pool = new OrderPool(1);
+        pool.borrowOrder();
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test covering `returnOrder(null)` to ensure it is a no-op and doesn’t affect pool size.

There’s logic for `order == null` in `returnOrder`, but no test covering it. Please add a test (e.g. `testReturnNullOrderIsNoOp`) that records `getAvailableCount()`, calls `returnOrder(null)`, and asserts the count is unchanged to guard against regressions in this path.
</issue_to_address>

### Comment 6
<location path="src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java" line_range="6" />
<code_context>
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class OrderPoolTest {
+
+    @Test
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a focused test for the `Order.clear()` contract, separate from pooling behavior.

Since `Order.clear()` underpins the pooling strategy, a dedicated unit test (e.g., in `OrderTest`) that fully populates an `Order`, calls `clear()`, and asserts every field is reset would help localize failures (Order vs. OrderPool) and reduce the chance of regressions that pool tests don’t catch.

Suggested implementation:

```java
        order.setSymbol("BTC/USD");
        pool.returnOrder(order);
        assertEquals(10, pool.getAvailableCount());
    }
}

class OrderTest {

    @Test
    void clear_resets_all_fields() {
        Order order = new Order();
        order.setSymbol("BTC/USD");

        order.clear();

        assertNull(order.getSymbol());
    }
}

```

To fully align with your review comment ("assert every field is reset"), extend `clear_resets_all_fields` to:
1. Populate every mutable field on `Order` (e.g., quantity, side, price, timestamps, flags, IDs) using whatever setters or builder methods `Order` exposes.
2. After `order.clear()`, assert the post-conditions for each field according to the `clear()` contract (e.g., null for references, 0 for numerics, false for booleans, default enums).
3. If `Order` has derived or cached fields, ensure they are also verified in this test so that regressions in `clear()` are caught independently of pooling behavior.
</issue_to_address>

### Comment 7
<location path="src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java" line_range="15" />
<code_context>
+ */
+@Slf4j
+@Component
+public class OrderPool {
+    private final BlockingQueue<Order> pool;
+    private static final int DEFAULT_POOL_SIZE = 1000;
</code_context>
<issue_to_address>
**issue (complexity):** Consider introducing an OrderProvider interface and simple alternative implementation so that pooling is hidden behind an abstraction and can be swapped out without touching calling code.

You can keep the pooling optimization but decouple it from the domain and make it easier to swap out with a simpler implementation.

### 1. Introduce an `OrderProvider` interface

Define a minimal abstraction that callers depend on instead of `OrderPool` directly:

```java
package com.castletrade.oms.core.domain.model;

public interface OrderProvider {
    Order borrowOrder();
    void returnOrder(Order order);
}
```

Then adapt `OrderPool` to implement this:

```java
@Slf4j
@Component
public class OrderPool implements OrderProvider {
    // existing implementation unchanged
}
```

All current consumers should be updated to depend on `OrderProvider` instead of `OrderPool`:

```java
// before
private final OrderPool orderPool;

// after
private final OrderProvider orderProvider;
```

```java
// before
Order order = orderPool.borrowOrder();
orderPool.returnOrder(order);

// after
Order order = orderProvider.borrowOrder();
orderProvider.returnOrder(order);
```

### 2. Add a trivial, non-pooled implementation

You can provide a simple, allocation-only implementation for cases where pooling isn’t needed (or for tests), without touching existing logic:

```java
@Component
@Primary // if you want this as the default
public class SimpleOrderProvider implements OrderProvider {

    @Override
    public Order borrowOrder() {
        return new Order();
    }

    @Override
    public void returnOrder(Order order) {
        // nothing to do
    }
}
```

Then `OrderPool` can be used only where really needed, or enabled via configuration/profile:

```java
@Component
@Profile("pooled-orders")
public class OrderPool implements OrderProvider {
    // current implementation
}
```

This keeps all current `OrderPool` behavior intact but:
- Moves complexity behind a narrow interface.
- Lets you keep the domain model focused on `Order` rather than pooling details.
- Allows you to simplify or even remove pooling later without touching call sites.
</issue_to_address>

Sourcery es gratis para proyectos open source - si te gustan nuestras revisiones por favor considera compartirlas ✨
¡Ayúdame a ser más útil! Por favor haz clic en 👍 o 👎 en cada comentario y usaré el feedback para mejorar tus revisiones.
Original comment in English

Hey - I've found 7 issues, and left some high level feedback:

  • The pool size is currently hardcoded (and the @component uses only the no-arg constructor); consider making the pool capacity externally configurable (e.g., via application properties / constructor injection) so it can be tuned per deployment.
  • Since Order instances are now mutable and reused across threads via the pool, it would be helpful to explicitly document in the OrderPool Javadoc that callers must not retain references or use an Order after returning it to the pool to avoid subtle concurrency bugs.
  • The warning log on pool exhaustion may become very noisy under sustained load; consider either reducing the log level, rate limiting, or logging the exhaustion state only when a threshold is crossed.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The pool size is currently hardcoded (and the @Component uses only the no-arg constructor); consider making the pool capacity externally configurable (e.g., via application properties / constructor injection) so it can be tuned per deployment.
- Since Order instances are now mutable and reused across threads via the pool, it would be helpful to explicitly document in the OrderPool Javadoc that callers must not retain references or use an Order after returning it to the pool to avoid subtle concurrency bugs.
- The warning log on pool exhaustion may become very noisy under sustained load; consider either reducing the log level, rate limiting, or logging the exhaustion state only when a threshold is crossed.

## Individual Comments

### Comment 1
<location path="src/main/java/com/castletrade/oms/core/domain/model/Order.java" line_range="29-38" />
<code_context>
+    /**
+     * Resets the order state for reuse in an object pool.
+     */
+    public void clear() {
+        this.id = null;
+        this.symbol = null;
+        this.assetClass = null;
+        this.side = null;
+        this.quantity = null;
+        this.price = null;
+        this.executionType = null;
+        this.status = null;
+        this.timestamp = null;
+    }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider restricting `clear()` visibility to limit misuse outside pooling contexts.

As a public method, `clear()` lets any caller reset an `Order` that may still be in use, which can cause subtle bugs (e.g., entries in collections suddenly losing their identity fields). Since it exists for pooling, consider making it package‑private or otherwise limiting visibility so only `OrderPool` can call it.

Suggested implementation:

```java
    /**
     * Resets the order state for reuse in an object pool.
     * Package-private to prevent misuse outside pooling contexts.
     */
    void clear() {
        this.id = null;
        this.symbol = null;
        this.assetClass = null;
        this.side = null;
        this.quantity = null;
        this.price = null;

```

1. Ensure any code (e.g., `OrderPool`) that invokes `clear()` resides in the same package `com.castletrade.oms.core.domain.model` or adjust its package accordingly.
2. If there are tests calling `order.clear()` from a different package, either move those tests into the same package or access it via the pooling abstraction instead of directly calling `clear()`.
</issue_to_address>

### Comment 2
<location path="src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java" line_range="31-39" />
<code_context>
+    /**
+     * Borrows an order from the pool.
+     */
+    public Order borrowOrder() {
+        Order order = pool.poll();
+        if (order == null) {
+            log.warn("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
+            return new Order();
+        }
</code_context>
<issue_to_address>
**suggestion (performance):** Using WARN for pool exhaustion in a hot path may generate excessive log noise under load.

In high-throughput scenarios, this condition may be hit frequently, so `WARN` here can both slow the hot path and flood logs. Consider lowering the level to `INFO`/`DEBUG` or adding throttling (e.g., log every Nth occurrence or on a time interval) to preserve observability without excessive log I/O.

```suggestion
    /**
     * Borrows an order from the pool.
     */
    public Order borrowOrder() {
        Order order = pool.poll();
        if (order == null) {
            log.debug("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
            return new Order();
        }
```
</issue_to_address>

### Comment 3
<location path="src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java" line_range="46-51" />
<code_context>
+    /**
+     * Returns an order to the pool after resetting its state.
+     */
+    public void returnOrder(Order order) {
+        if (order == null) return;
+        order.clear();
+        boolean accepted = pool.offer(order);
+        if (!accepted) {
+            log.debug("OrderPool full, discarding order object.");
+        }
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Returning the same `Order` instance multiple times can lead to multiple references to the same object in the pool.

Since `ArrayBlockingQueue` allows duplicates, the same `Order` can be added to the pool multiple times if `returnOrder` is called twice on it (or on an object never borrowed). That can lead to the same mutable instance being handed to multiple borrowers concurrently. If this is possible in your usage, add a guard (e.g., an `inPool` flag on `Order` or a small tracking structure) to prevent double returns or foreign objects being returned.
</issue_to_address>

### Comment 4
<location path="src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java" line_range="9-18" />
<code_context>
+class OrderPoolTest {
+
+    @Test
+    void testBorrowAndReturn() {
+        OrderPool pool = new OrderPool(10);
+        assertEquals(10, pool.getAvailableCount());
+
+        Order order = pool.borrowOrder();
+        assertNotNull(order);
+        assertEquals(9, pool.getAvailableCount());
+
+        order.setSymbol("BTC/USD");
+        pool.returnOrder(order);
+
+        assertEquals(10, pool.getAvailableCount());
+        
+        Order reborrowed = pool.borrowOrder();
+        assertNull(reborrowed.getSymbol(), "Order should be cleared when returned to pool");
+    }
+
</code_context>
<issue_to_address>
**suggestion (testing):** Extend the borrow/return test to verify all Order fields are cleared, not just symbol.

Since `clear()` resets all fields (`id`, `assetClass`, `side`, `quantity`, `price`, `executionType`, `status`, `timestamp`), initialize several of these before `returnOrder` and assert they are all cleared on re-borrow. This strengthens the test to catch future regressions where a field is accidentally not reset.

Suggested implementation:

```java
    @Test
    void testBorrowAndReturn() {
        OrderPool pool = new OrderPool(10);
        assertEquals(10, pool.getAvailableCount());

        Order order = pool.borrowOrder();
        assertNotNull(order);
        assertEquals(9, pool.getAvailableCount());

        // initialize multiple fields so we can verify they are cleared
        order.setId(123L);
        order.setSymbol("BTC/USD");
        order.setAssetClass(AssetClass.CRYPTO);
        order.setSide(Side.BUY);
        order.setQuantity(new java.math.BigDecimal("1.23"));
        order.setPrice(new java.math.BigDecimal("45678.90"));
        order.setExecutionType(ExecutionType.MARKET);
        order.setStatus(OrderStatus.NEW);
        order.setTimestamp(java.time.Instant.now());

        pool.returnOrder(order);

        assertEquals(10, pool.getAvailableCount());

        Order reborrowed = pool.borrowOrder();

        // all fields should be cleared when returned to pool
        assertNull(reborrowed.getId(), "Order id should be cleared when returned to pool");
        assertNull(reborrowed.getSymbol(), "Order symbol should be cleared when returned to pool");
        assertNull(reborrowed.getAssetClass(), "Order assetClass should be cleared when returned to pool");
        assertNull(reborrowed.getSide(), "Order side should be cleared when returned to pool");
        assertNull(reborrowed.getQuantity(), "Order quantity should be cleared when returned to pool");
        assertNull(reborrowed.getPrice(), "Order price should be cleared when returned to pool");
        assertNull(reborrowed.getExecutionType(), "Order executionType should be cleared when returned to pool");
        assertNull(reborrowed.getStatus(), "Order status should be cleared when returned to pool");
        assertNull(reborrowed.getTimestamp(), "Order timestamp should be cleared when returned to pool");
    }

```

- Ensure the `Order` class exposes getters/setters matching the method names used above: `getId`, `setId`, `getAssetClass`, `setAssetClass`, `getSide`, `setSide`, `getQuantity`, `setQuantity`, `getPrice`, `setPrice`, `getExecutionType`, `setExecutionType`, `getStatus`, `setStatus`, `getTimestamp`, `setTimestamp`.
- Adjust enum/type names if your domain model uses different ones (e.g. `OrderAssetClass` instead of `AssetClass`, `OrderSide` instead of `Side`, `ExecutionType`/`OrderExecutionType`, `OrderStatus` instead of `OrderStatus`, and `Instant` vs another timestamp type).
- If any of these fields are primitives rather than nullable reference types, change the corresponding assertions to check for the appropriate reset default (e.g. `assertEquals(0L, reborrowed.getId())`) instead of `assertNull`.
- You may also want to add static imports for `BigDecimal` and `Instant` (or your timestamp type) instead of using fully-qualified names if that better matches your test code style.
</issue_to_address>

### Comment 5
<location path="src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java" line_range="8-17" />
<code_context>
+
+class OrderPoolTest {
+
+    @Test
+    void testBorrowAndReturn() {
+        OrderPool pool = new OrderPool(10);
+        assertEquals(10, pool.getAvailableCount());
+
+        Order order = pool.borrowOrder();
+        assertNotNull(order);
+        assertEquals(9, pool.getAvailableCount());
+
+        order.setSymbol("BTC/USD");
+        pool.returnOrder(order);
+
+        assertEquals(10, pool.getAvailableCount());
+        
+        Order reborrowed = pool.borrowOrder();
+        assertNull(reborrowed.getSymbol(), "Order should be cleared when returned to pool");
+    }
+
+    @Test
+    void testPoolExhaustion() {
+        OrderPool pool = new OrderPool(1);
+        pool.borrowOrder();
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test covering `returnOrder(null)` to ensure it is a no-op and doesn’t affect pool size.

There’s logic for `order == null` in `returnOrder`, but no test covering it. Please add a test (e.g. `testReturnNullOrderIsNoOp`) that records `getAvailableCount()`, calls `returnOrder(null)`, and asserts the count is unchanged to guard against regressions in this path.
</issue_to_address>

### Comment 6
<location path="src/test/java/com/castletrade/oms/core/domain/model/OrderPoolTest.java" line_range="6" />
<code_context>
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+class OrderPoolTest {
+
+    @Test
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding a focused test for the `Order.clear()` contract, separate from pooling behavior.

Since `Order.clear()` underpins the pooling strategy, a dedicated unit test (e.g., in `OrderTest`) that fully populates an `Order`, calls `clear()`, and asserts every field is reset would help localize failures (Order vs. OrderPool) and reduce the chance of regressions that pool tests don’t catch.

Suggested implementation:

```java
        order.setSymbol("BTC/USD");
        pool.returnOrder(order);
        assertEquals(10, pool.getAvailableCount());
    }
}

class OrderTest {

    @Test
    void clear_resets_all_fields() {
        Order order = new Order();
        order.setSymbol("BTC/USD");

        order.clear();

        assertNull(order.getSymbol());
    }
}

```

To fully align with your review comment ("assert every field is reset"), extend `clear_resets_all_fields` to:
1. Populate every mutable field on `Order` (e.g., quantity, side, price, timestamps, flags, IDs) using whatever setters or builder methods `Order` exposes.
2. After `order.clear()`, assert the post-conditions for each field according to the `clear()` contract (e.g., null for references, 0 for numerics, false for booleans, default enums).
3. If `Order` has derived or cached fields, ensure they are also verified in this test so that regressions in `clear()` are caught independently of pooling behavior.
</issue_to_address>

### Comment 7
<location path="src/main/java/com/castletrade/oms/core/domain/model/OrderPool.java" line_range="15" />
<code_context>
+ */
+@Slf4j
+@Component
+public class OrderPool {
+    private final BlockingQueue<Order> pool;
+    private static final int DEFAULT_POOL_SIZE = 1000;
</code_context>
<issue_to_address>
**issue (complexity):** Consider introducing an OrderProvider interface and simple alternative implementation so that pooling is hidden behind an abstraction and can be swapped out without touching calling code.

You can keep the pooling optimization but decouple it from the domain and make it easier to swap out with a simpler implementation.

### 1. Introduce an `OrderProvider` interface

Define a minimal abstraction that callers depend on instead of `OrderPool` directly:

```java
package com.castletrade.oms.core.domain.model;

public interface OrderProvider {
    Order borrowOrder();
    void returnOrder(Order order);
}
```

Then adapt `OrderPool` to implement this:

```java
@Slf4j
@Component
public class OrderPool implements OrderProvider {
    // existing implementation unchanged
}
```

All current consumers should be updated to depend on `OrderProvider` instead of `OrderPool`:

```java
// before
private final OrderPool orderPool;

// after
private final OrderProvider orderProvider;
```

```java
// before
Order order = orderPool.borrowOrder();
orderPool.returnOrder(order);

// after
Order order = orderProvider.borrowOrder();
orderProvider.returnOrder(order);
```

### 2. Add a trivial, non-pooled implementation

You can provide a simple, allocation-only implementation for cases where pooling isn’t needed (or for tests), without touching existing logic:

```java
@Component
@Primary // if you want this as the default
public class SimpleOrderProvider implements OrderProvider {

    @Override
    public Order borrowOrder() {
        return new Order();
    }

    @Override
    public void returnOrder(Order order) {
        // nothing to do
    }
}
```

Then `OrderPool` can be used only where really needed, or enabled via configuration/profile:

```java
@Component
@Profile("pooled-orders")
public class OrderPool implements OrderProvider {
    // current implementation
}
```

This keeps all current `OrderPool` behavior intact but:
- Moves complexity behind a narrow interface.
- Lets you keep the domain model focused on `Order` rather than pooling details.
- Allows you to simplify or even remove pooling later without touching call sites.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +29 to +38
public void clear() {
this.id = null;
this.symbol = null;
this.assetClass = null;
this.side = null;
this.quantity = null;
this.price = null;
this.executionType = null;
this.status = null;
this.timestamp = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Considera restringir la visibilidad de clear() para limitar su mal uso fuera de los contextos de pool.

Como método público, clear() permite que cualquier consumidor reinicie un Order que puede seguir en uso, lo que puede causar errores sutiles (por ejemplo, entradas en colecciones que de repente pierden sus campos de identidad). Dado que existe para el uso con el pool, considera hacerlo con visibilidad de paquete (package‑private) o limitar su visibilidad de otra forma de modo que solo OrderPool pueda llamarlo.

Implementación sugerida:

    /**
     * Resets the order state for reuse in an object pool.
     * Package-private to prevent misuse outside pooling contexts.
     */
    void clear() {
        this.id = null;
        this.symbol = null;
        this.assetClass = null;
        this.side = null;
        this.quantity = null;
        this.price = null;
  1. Asegúrate de que cualquier código (por ejemplo, OrderPool) que invoque clear() resida en el mismo paquete com.castletrade.oms.core.domain.model o ajusta su paquete en consecuencia.
  2. Si hay tests que llaman a order.clear() desde un paquete diferente, mueve esos tests al mismo paquete o accede a través de la abstracción de pooling en lugar de llamar a clear() directamente.
Original comment in English

suggestion (bug_risk): Consider restricting clear() visibility to limit misuse outside pooling contexts.

As a public method, clear() lets any caller reset an Order that may still be in use, which can cause subtle bugs (e.g., entries in collections suddenly losing their identity fields). Since it exists for pooling, consider making it package‑private or otherwise limiting visibility so only OrderPool can call it.

Suggested implementation:

    /**
     * Resets the order state for reuse in an object pool.
     * Package-private to prevent misuse outside pooling contexts.
     */
    void clear() {
        this.id = null;
        this.symbol = null;
        this.assetClass = null;
        this.side = null;
        this.quantity = null;
        this.price = null;
  1. Ensure any code (e.g., OrderPool) that invokes clear() resides in the same package com.castletrade.oms.core.domain.model or adjust its package accordingly.
  2. If there are tests calling order.clear() from a different package, either move those tests into the same package or access it via the pooling abstraction instead of directly calling clear().

Comment on lines +31 to +39
/**
* Borrows an order from the pool.
*/
public Order borrowOrder() {
Order order = pool.poll();
if (order == null) {
log.warn("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
return new Order();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Usar WARN para el agotamiento del pool en una ruta caliente puede generar un exceso de ruido en los logs bajo carga.

En escenarios de alto rendimiento, esta condición puede producirse con frecuencia, así que WARN aquí puede tanto ralentizar la ruta caliente como inundar los logs. Considera bajar el nivel a INFO/DEBUG o añadir limitación de frecuencia (por ejemplo, registrar cada N ocurrencias o en un intervalo de tiempo) para mantener la observabilidad sin un E/S de logs excesivo.

Suggested change
/**
* Borrows an order from the pool.
*/
public Order borrowOrder() {
Order order = pool.poll();
if (order == null) {
log.warn("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
return new Order();
}
/**
* Borrows an order from the pool.
*/
public Order borrowOrder() {
Order order = pool.poll();
if (order == null) {
log.debug("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
return new Order();
}
Original comment in English

suggestion (performance): Using WARN for pool exhaustion in a hot path may generate excessive log noise under load.

In high-throughput scenarios, this condition may be hit frequently, so WARN here can both slow the hot path and flood logs. Consider lowering the level to INFO/DEBUG or adding throttling (e.g., log every Nth occurrence or on a time interval) to preserve observability without excessive log I/O.

Suggested change
/**
* Borrows an order from the pool.
*/
public Order borrowOrder() {
Order order = pool.poll();
if (order == null) {
log.warn("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
return new Order();
}
/**
* Borrows an order from the pool.
*/
public Order borrowOrder() {
Order order = pool.poll();
if (order == null) {
log.debug("OrderPool exhausted, creating new transient object. Consider increasing pool size.");
return new Order();
}

Comment on lines +46 to +51
public void returnOrder(Order order) {
if (order == null) return;
order.clear();
boolean accepted = pool.offer(order);
if (!accepted) {
log.debug("OrderPool full, discarding order object.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Devolver la misma instancia de Order varias veces puede llevar a múltiples referencias al mismo objeto en el pool.

Dado que ArrayBlockingQueue permite duplicados, el mismo Order puede añadirse al pool varias veces si se llama a returnOrder dos veces sobre él (o sobre un objeto que nunca se pidió prestado). Eso puede llevar a que la misma instancia mutable se entregue a múltiples consumidores simultáneamente. Si esto es posible en tu caso de uso, añade una protección (por ejemplo, un flag inPool en Order o una pequeña estructura de seguimiento) para evitar devoluciones dobles u objetos externos que se devuelvan.

Original comment in English

issue (bug_risk): Returning the same Order instance multiple times can lead to multiple references to the same object in the pool.

Since ArrayBlockingQueue allows duplicates, the same Order can be added to the pool multiple times if returnOrder is called twice on it (or on an object never borrowed). That can lead to the same mutable instance being handed to multiple borrowers concurrently. If this is possible in your usage, add a guard (e.g., an inPool flag on Order or a small tracking structure) to prevent double returns or foreign objects being returned.

*/
@Slf4j
@Component
public class OrderPool {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Considera introducir una interfaz OrderProvider y una implementación alternativa sencilla para que el pooling quede oculto tras una abstracción y pueda sustituirse sin tocar el código consumidor.

Puedes mantener la optimización de pooling pero desacoplarla del dominio y facilitar su sustitución por una implementación más simple.

1. Introduce una interfaz OrderProvider

Define una abstracción mínima de la que dependan los consumidores en lugar de depender directamente de OrderPool:

package com.castletrade.oms.core.domain.model;

public interface OrderProvider {
    Order borrowOrder();
    void returnOrder(Order order);
}

Después, adapta OrderPool para que la implemente:

@Slf4j
@Component
public class OrderPool implements OrderProvider {
    // existing implementation unchanged
}

Todos los consumidores actuales deberían actualizarse para depender de OrderProvider en lugar de OrderPool:

// before
private final OrderPool orderPool;

// after
private final OrderProvider orderProvider;
// before
Order order = orderPool.borrowOrder();
orderPool.returnOrder(order);

// after
Order order = orderProvider.borrowOrder();
orderProvider.returnOrder(order);

2. Añade una implementación trivial sin pool

Puedes proporcionar una implementación sencilla, basada solo en asignaciones, para casos en los que el pooling no sea necesario (o para tests), sin tocar la lógica existente:

@Component
@Primary // if you want this as the default
public class SimpleOrderProvider implements OrderProvider {

    @Override
    public Order borrowOrder() {
        return new Order();
    }

    @Override
    public void returnOrder(Order order) {
        // nothing to do
    }
}

Después, OrderPool puede usarse solo donde realmente se necesite, o habilitarse mediante configuración/perfil:

@Component
@Profile("pooled-orders")
public class OrderPool implements OrderProvider {
    // current implementation
}

Esto mantiene intacto todo el comportamiento actual de OrderPool, pero:

  • Mueve la complejidad detrás de una interfaz estrecha.
  • Te permite mantener el modelo de dominio centrado en Order en lugar de en detalles de pooling.
  • Permite simplificar o incluso eliminar el pooling más adelante sin tocar los puntos de uso.
Original comment in English

issue (complexity): Consider introducing an OrderProvider interface and simple alternative implementation so that pooling is hidden behind an abstraction and can be swapped out without touching calling code.

You can keep the pooling optimization but decouple it from the domain and make it easier to swap out with a simpler implementation.

1. Introduce an OrderProvider interface

Define a minimal abstraction that callers depend on instead of OrderPool directly:

package com.castletrade.oms.core.domain.model;

public interface OrderProvider {
    Order borrowOrder();
    void returnOrder(Order order);
}

Then adapt OrderPool to implement this:

@Slf4j
@Component
public class OrderPool implements OrderProvider {
    // existing implementation unchanged
}

All current consumers should be updated to depend on OrderProvider instead of OrderPool:

// before
private final OrderPool orderPool;

// after
private final OrderProvider orderProvider;
// before
Order order = orderPool.borrowOrder();
orderPool.returnOrder(order);

// after
Order order = orderProvider.borrowOrder();
orderProvider.returnOrder(order);

2. Add a trivial, non-pooled implementation

You can provide a simple, allocation-only implementation for cases where pooling isn’t needed (or for tests), without touching existing logic:

@Component
@Primary // if you want this as the default
public class SimpleOrderProvider implements OrderProvider {

    @Override
    public Order borrowOrder() {
        return new Order();
    }

    @Override
    public void returnOrder(Order order) {
        // nothing to do
    }
}

Then OrderPool can be used only where really needed, or enabled via configuration/profile:

@Component
@Profile("pooled-orders")
public class OrderPool implements OrderProvider {
    // current implementation
}

This keeps all current OrderPool behavior intact but:

  • Moves complexity behind a narrow interface.
  • Lets you keep the domain model focused on Order rather than pooling details.
  • Allows you to simplify or even remove pooling later without touching call sites.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant