You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2024/11/12 08:34:42 UTC

(causeway) branch v3 updated: CAUSEWAY-2297: less ceremony for ChainOfResponsibility (convert to record)

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v3
in repository https://gitbox.apache.org/repos/asf/causeway.git


The following commit(s) were added to refs/heads/v3 by this push:
     new 0df120f93a9 CAUSEWAY-2297: less ceremony for ChainOfResponsibility (convert to record)
0df120f93a9 is described below

commit 0df120f93a90cbf7d11626cfe32070e3d641d5aa
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Nov 12 09:34:38 2024 +0100

    CAUSEWAY-2297: less ceremony for ChainOfResponsibility (convert to
    record)
    
    - also easer to debug
    - encapsulate object manager helper classes
---
 .../commons/handler/ChainOfResponsibility.java     |  73 +++++------
 .../handlers/ChainOfResponsibilityTest.java        |   9 +-
 .../config/beans/CausewayComponentCollector.java   |  28 +---
 .../metamodel/CausewayModuleCoreMetamodel.java     |   4 +-
 .../_testing/MetaModelContext_forTesting.java      |   3 +-
 .../core/metamodel/context/MetaModelContext.java   |   6 +-
 .../metamodel/context/MetaModelContextFactory.java |   6 +
 .../choices/ChoicesFacetFromBoundedAbstract.java   |   4 +-
 .../metamodel/objectmanager/ObjectBulkLoader.java  |  66 +++-------
 .../metamodel/objectmanager/ObjectCreator.java     | 101 ++++++---------
 .../core/metamodel/objectmanager/ObjectLoader.java |  34 +----
 .../metamodel/objectmanager/ObjectManager.java     | 141 +++++++++++++++------
 .../objectmanager/ObjectManagerDefault.java        | 116 -----------------
 .../core/metamodel/tabular/simple/DataTable.java   |   4 +-
 .../repository/RepositoryServiceDefault.java       |   6 +-
 15 files changed, 229 insertions(+), 372 deletions(-)

diff --git a/commons/src/main/java/org/apache/causeway/commons/handler/ChainOfResponsibility.java b/commons/src/main/java/org/apache/causeway/commons/handler/ChainOfResponsibility.java
index 1c456572bb8..25bf77afc2f 100644
--- a/commons/src/main/java/org/apache/causeway/commons/handler/ChainOfResponsibility.java
+++ b/commons/src/main/java/org/apache/causeway/commons/handler/ChainOfResponsibility.java
@@ -20,6 +20,10 @@ package org.apache.causeway.commons.handler;
 
 import java.util.List;
 
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.base._Strings;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 
 /**
@@ -33,15 +37,9 @@ import org.apache.causeway.commons.internal.exceptions._Exceptions;
  * @param <X> request type
  * @param <R> response type
  */
-public interface ChainOfResponsibility<X, R> {
-
-    /**
-     * The {@code request} is passed along the chain of handlers, until one of them handles the request.
-     * @param request
-     * @return response of the first handler that handled the request wrapped in an Optional,
-     * or an empty Optional, if no handler handled the request
-     */
-    R handle(X request);
+public record ChainOfResponsibility<X, R>(
+        String name,
+        Can<? extends Handler<X, R>> handlers) {
 
     /**
      * A chain of responsibility is made up of handlers, that are asked in sequence,
@@ -52,48 +50,39 @@ public interface ChainOfResponsibility<X, R> {
      * @param <X> request type
      * @param <R> response type
      */
-    static interface Handler<X, R> {
+    public static interface Handler<X, R> {
         boolean isHandling(X request);
         R handle(X request);
     }
 
-    /**
-     * Creates a {@link ChainOfResponsibility} for given {@code chainOfHandlers}
-     */
-    static <X, R> ChainOfResponsibility<X, R>
-    of(final List<? extends ChainOfResponsibility.Handler<? super X, R>> chainOfHandlers) {
+    public ChainOfResponsibility(@Nullable final String name, @Nullable final Can<? extends Handler<X, R>> handlers) {
+        this.name = _Strings.nonEmpty(name).orElse("unnamed chain");
+        this.handlers = handlers!=null
+                ? handlers
+                : Can.empty();
+    }
+
+    // could be widened to SequencedCollection once available
+    public ChainOfResponsibility(final String name, @Nullable final List<? extends Handler<X, R>> handlers) {
+        this(name, Can.ofCollection(handlers));
+    }
 
-        return new ChainOfResponsibility<X, R>(){
-            @Override
-            public R handle(final X request) {
-                return chainOfHandlers.stream()
-                    .filter(h -> h.isHandling(request))
-                    .findFirst()
-                    .orElseThrow(()->
-                        _Exceptions.noSuchElement("no handler found for request %s", request))
-                    .handle(request);
-            }
-        };
+    public ChainOfResponsibility(final String name, @Nullable final Handler<X, R>[] handlers) {
+        this(name, Can.ofArray(handlers));
     }
 
     /**
-     * Creates a named {@link ChainOfResponsibility} for given {@code chainOfHandlers}
+     * The {@code request} is passed along the chain of handlers, until one of them handles the request.
+     * @param request
+     * @return response of the first handler that handled the request wrapped in an Optional,
+     * or an empty Optional, if no handler handled the request
      */
-    static <X, R> ChainOfResponsibility<X, R>
-    named(  final String name,
-            final List<? extends ChainOfResponsibility.Handler<? super X, R>> chainOfHandlers) {
-
-    return new ChainOfResponsibility<X, R>(){
-        @Override
-        public R handle(final X request) {
-            return chainOfHandlers.stream()
-                .filter(h -> h.isHandling(request))
-                .findFirst()
-                .orElseThrow(()->
-                    _Exceptions.noSuchElement("no %s handler found for request %s", name, request))
-                .handle(request);
+    public R handle(final X request) {
+        for(var handler : handlers) {
+            if(!handler.isHandling(request)) continue;
+            return handler.handle(request);
         }
-    };
-}
+        throw _Exceptions.noSuchElement("no handler found for request %s", request);
+    }
 
 }
diff --git a/commons/src/test/java/org/apache/causeway/commons/handlers/ChainOfResponsibilityTest.java b/commons/src/test/java/org/apache/causeway/commons/handlers/ChainOfResponsibilityTest.java
index ae0188a38fd..d5abe9f043a 100644
--- a/commons/src/test/java/org/apache/causeway/commons/handlers/ChainOfResponsibilityTest.java
+++ b/commons/src/test/java/org/apache/causeway/commons/handlers/ChainOfResponsibilityTest.java
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.handler.ChainOfResponsibility;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 
@@ -76,7 +77,7 @@ class ChainOfResponsibilityTest {
 
         };
 
-        var chainOfResponsibility = ChainOfResponsibility.of(
+        var chainOfResponsibility = new ChainOfResponsibility<>("test",
                 Arrays.asList(aToUpperCase, bToUpperCase, finallyNoop));
 
         assertEquals("ASTRING", chainOfResponsibility.handle("aString")); // handled by first handler
@@ -107,9 +108,7 @@ class ChainOfResponsibilityTest {
 
         };
 
-        var handlers = Arrays.asList(aToUpperCase);
-
-        var chainOfResponsibility = ChainOfResponsibility.of(handlers);
+        var chainOfResponsibility = new ChainOfResponsibility<>("test", Can.of(aToUpperCase));
 
         assertEquals("ASTRING", chainOfResponsibility.handle("aString")); // handled by first handler
         assertThrows(NoSuchElementException.class, ()->chainOfResponsibility.handle("xxx")); // not handled
@@ -132,7 +131,7 @@ class ChainOfResponsibilityTest {
 
         };
 
-        var chainOfResponsibility = ChainOfResponsibility.of(
+        var chainOfResponsibility = new ChainOfResponsibility<>("test",
                 Arrays.asList(throwingHandler));
 
         assertThrows(RuntimeException.class, ()->chainOfResponsibility.handle("throw"));
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayComponentCollector.java b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayComponentCollector.java
index 1a1a2e461d0..bd16af4db73 100644
--- a/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayComponentCollector.java
+++ b/core/config/src/main/java/org/apache/causeway/core/config/beans/CausewayComponentCollector.java
@@ -66,15 +66,10 @@ record CausewayComponentCollector(
 
         switch (typeMeta.managedBy()) {
             case NONE, CAUSEWAY, PERSISTENCE -> {
-                remove(beanDefinitionName);
+                registry.removeBeanDefinition(beanDefinitionName);
+                log.debug("vetoing bean {}", beanDefinitionName);
             }
-            case UNSPECIFIED -> {
-                if(isRenamed) {
-                    //rename(beanDefinition, beanDefinitionName, typeMeta.logicalType().logicalName()); //TODO does not work yet
-                    classCache().setSpringNamed(beanClass, beanDefinitionName);
-                }
-            }
-            case SPRING -> {
+            case UNSPECIFIED, SPRING -> {
                 if(isRenamed) {
                     // renaming not allowed, report back to class-cache
                     classCache().setSpringNamed(beanClass, beanDefinitionName);
@@ -84,7 +79,7 @@ record CausewayComponentCollector(
     }
 
     // -- HELPER
-    
+
     /**
      * Allows for the given type-meta to be modified before bean-definition registration
      * is finalized by Spring, immediately after the type-scan phase.
@@ -92,7 +87,7 @@ record CausewayComponentCollector(
      * <br> - Whether given {@link Component} annotated or meta-annotated type should be made
      * available for injection.
      * <br> - Naming strategy to override that of Spring.
-     * @param logicalType 
+     * @param logicalType
      *
      * @apiNote implementing classes might have side effects, eg. intercept
      * discovered types into a type registry
@@ -111,17 +106,6 @@ record CausewayComponentCollector(
         }
         return typeMeta;
     }
-    
-    private void remove(String beanDefinitionName) {
-        registry.removeBeanDefinition(beanDefinitionName);
-        log.debug("vetoing bean {}", beanDefinitionName);
-    }
-    
-    private void rename(BeanDefinition beanDefinition, String beanDefinitionName, String beanNameOverride) {
-        registry.removeBeanDefinition(beanDefinitionName);
-        registry.registerBeanDefinition(beanNameOverride, beanDefinition);
-        log.debug("renaming bean {} -> {}", beanDefinitionName, beanNameOverride);
-    }
 
     private static Optional<Class<?>> loadClass(@Nullable final BeanDefinition beanDefinition) {
         return Optional.ofNullable(beanDefinition.getBeanClassName())
@@ -146,7 +130,7 @@ record CausewayComponentCollector(
             return Optional.empty();
         }
     }
-    
+
     private _ClassCache classCache() {
         return causewayBeanTypeClassifier.classCache();
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java
index 0280660bbda..fa8b0a40415 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/CausewayModuleCoreMetamodel.java
@@ -46,9 +46,7 @@ import org.apache.causeway.core.metamodel.inspect.model.MemberNode;
 import org.apache.causeway.core.metamodel.inspect.model.ParameterNode;
 import org.apache.causeway.core.metamodel.inspect.model.PropertyNode;
 import org.apache.causeway.core.metamodel.inspect.model.TypeNode;
-import org.apache.causeway.core.metamodel.objectmanager.ObjectManagerDefault;
 import org.apache.causeway.core.metamodel.progmodel.ProgrammingModelInitFilterDefault;
-import org.apache.causeway.core.metamodel.services.inject.ServiceInjectorDefault;
 import org.apache.causeway.core.metamodel.services.appfeat.ApplicationFeatureRepositoryDefault;
 import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutorDefault;
 import org.apache.causeway.core.metamodel.services.classsubstitutor.ClassSubstitutorForCollections;
@@ -63,6 +61,7 @@ import org.apache.causeway.core.metamodel.services.grid.bootstrap.GridMarshaller
 import org.apache.causeway.core.metamodel.services.grid.bootstrap.GridSystemServiceBootstrap;
 import org.apache.causeway.core.metamodel.services.grid.spi.LayoutResourceLoaderDefault;
 import org.apache.causeway.core.metamodel.services.idstringifier.IdStringifierLookupService;
+import org.apache.causeway.core.metamodel.services.inject.ServiceInjectorDefault;
 import org.apache.causeway.core.metamodel.services.layout.LayoutServiceDefault;
 import org.apache.causeway.core.metamodel.services.metamodel.MetaModelServiceDefault;
 import org.apache.causeway.core.metamodel.services.registry.ServiceRegistryDefault;
@@ -191,7 +190,6 @@ import lombok.NonNull;
         LayoutServiceDefault.class,
         MetamodelEventService.class,
         MetaModelServiceDefault.class,
-        ObjectManagerDefault.class,
         ProgrammingModelServiceDefault.class,
         ServiceInjectorDefault.class,
         ServiceRegistryDefault.class,
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java
index f73a25c500b..e0ec4ec11f6 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/_testing/MetaModelContext_forTesting.java
@@ -77,7 +77,6 @@ import org.apache.causeway.core.metamodel.facets.object.icon.ObjectIconService;
 import org.apache.causeway.core.metamodel.facets.object.value.annotcfg.ValueFacetForValueAnnotationOrAnyMatchingValueSemanticsFacetFactory;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
-import org.apache.causeway.core.metamodel.objectmanager.ObjectManagerDefault;
 import org.apache.causeway.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.causeway.core.metamodel.progmodel.ProgrammingModelAbstract;
 import org.apache.causeway.core.metamodel.progmodel.ProgrammingModelInitFilterDefault;
@@ -408,7 +407,7 @@ extends MetaModelContext {
     @Override
     public ObjectManager getObjectManager() {
         if(objectManager==null) {
-            objectManager = ObjectManagerDefault.forTesting(this);
+            objectManager = new ObjectManager(this);
         }
         return objectManager;
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext.java
index ffa80f5aafa..cacd91b0e46 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContext.java
@@ -24,12 +24,14 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.applib.services.i18n.TranslationService;
+import org.apache.causeway.commons.internal.annotations.BeanInternal;
 import org.apache.causeway.commons.internal.assertions._Assert;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 
 /**
  * @since 2.0
  */
+@BeanInternal
 public abstract class MetaModelContext implements HasMetaModelContext {
 
     @Override
@@ -52,9 +54,9 @@ public abstract class MetaModelContext implements HasMetaModelContext {
         return instance()
                 .orElseThrow(()->_Exceptions.noSuchElement("MetaModelContext not yet or no longer available."));
     }
-    
+
     // -- UTILITY
-    
+
     public static  TranslationService translationServiceOrFallback() {
         return MetaModelContext.instance()
                 .map(MetaModelContext::getTranslationService)
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContextFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContextFactory.java
index eb35aa47f03..d031de3c42d 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContextFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/context/MetaModelContextFactory.java
@@ -26,6 +26,7 @@ import org.springframework.context.annotation.Primary;
 
 import org.apache.causeway.core.config.environment.CausewaySystemEnvironment;
 import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
+import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
 
 /**
  *
@@ -52,6 +53,11 @@ public class MetaModelContextFactory {
         return mmc;
     }
 
+    @Bean
+    public ObjectManager objectManager(final MetaModelContext mmc) {
+        return new ObjectManager(mmc);
+    }
+
     // -- HELPER
 
     /**
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java
index e8a2187cbd0..c4231d7305f 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/choices/ChoicesFacetFromBoundedAbstract.java
@@ -35,7 +35,7 @@ import org.apache.causeway.core.metamodel.interactions.ValidatingInteractionAdvi
 import org.apache.causeway.core.metamodel.interactions.ValidityContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.MmVisibilityUtils;
-import org.apache.causeway.core.metamodel.objectmanager.ObjectBulkLoader;
+import org.apache.causeway.core.metamodel.objectmanager.ObjectManager.BulkLoadRequest;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 
 import lombok.SneakyThrows;
@@ -114,7 +114,7 @@ implements
         var query = Query.allInstances(resulType);
 
         var resultTypeSpec = specForType(resulType).orElse(null);
-        var queryRequest = ObjectBulkLoader.Request.of(resultTypeSpec, query);
+        var queryRequest = new BulkLoadRequest(resultTypeSpec, query);
         var allMatching = getObjectManager().queryObjects(queryRequest)
                 .filter(MmVisibilityUtils.filterOn(interactionInitiatedBy));
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectBulkLoader.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectBulkLoader.java
index 16ddeb3868c..9c4c1367192 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectBulkLoader.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectBulkLoader.java
@@ -18,62 +18,34 @@
  */
 package org.apache.causeway.core.metamodel.objectmanager;
 
-import java.util.List;
-
-import org.apache.causeway.applib.query.Query;
 import org.apache.causeway.commons.collections.Can;
 import org.apache.causeway.commons.handler.ChainOfResponsibility;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
-
-import lombok.Value;
+import org.apache.causeway.core.metamodel.objectmanager.ObjectManager.BulkLoadRequest;
 
-/**
- * @since 2.0
- */
-public interface ObjectBulkLoader {
+import lombok.NonNull;
 
-    Can<ManagedObject> loadObject(Request objectQuery);
+record ObjectBulkLoader(ChainOfResponsibility<BulkLoadRequest, Can<ManagedObject>> chain) {
 
-    // -- REQUEST (VALUE) TYPE
-
-    @Value(staticConstructor = "of")
-    public static class Request {
-        ObjectSpecification objectSpecification;
-        Query<?> query;
+    ObjectBulkLoader() {
+        this(new ChainOfResponsibility<>("ObjectBulkLoader", BuiltinHandlers.values()));
     }
 
-    // -- HANDLER
-
-    static interface Handler
-    extends
-        ChainOfResponsibility.Handler<ObjectBulkLoader.Request, Can<ManagedObject>> {
-    }
-
-    // -- FACTORY
-
-    public static ObjectBulkLoader createDefault(final MetaModelContext mmc) {
-        return request ->
-            ChainOfResponsibility.named(
-                    "ObjectBulkLoader",
-                    handlers)
-                .handle(request);
+    Can<ManagedObject> loadObject(@NonNull final BulkLoadRequest objectQuery) {
+        return chain.handle(objectQuery);
     }
 
     // -- HANDLERS
 
-    static final List<Handler> handlers = List.of(BuiltinHandlers.values());
-
-    enum BuiltinHandlers implements Handler {
+    enum BuiltinHandlers implements ChainOfResponsibility.Handler<BulkLoadRequest, Can<ManagedObject>> {
         GuardAgainstNull {
             @Override
-            public boolean isHandling(final ObjectBulkLoader.Request objectQuery) {
+            public boolean isHandling(final BulkLoadRequest objectQuery) {
                 if(objectQuery==null) {
                     return true;
                 }
-                var spec = objectQuery.getObjectSpecification();
+                var spec = objectQuery.objectSpecification();
                 if(spec == null) {
                     // eg "NONEXISTENT:123"
                     return true;
@@ -83,36 +55,36 @@ public interface ObjectBulkLoader {
                 return false;
             }
             @Override
-            public Can<ManagedObject> handle(final ObjectBulkLoader.Request objectQuery) {
+            public Can<ManagedObject> handle(final BulkLoadRequest objectQuery) {
                 return Can.empty();
             }
         },
         BulkLoadEntity {
             @Override
-            public boolean isHandling(final ObjectBulkLoader.Request objectQuery) {
-                var spec = objectQuery.getObjectSpecification();
+            public boolean isHandling(final BulkLoadRequest objectQuery) {
+                var spec = objectQuery.objectSpecification();
                 return spec.isEntity();
             }
             @Override
-            public Can<ManagedObject> handle(final ObjectBulkLoader.Request objectQuery) {
-                var spec = objectQuery.getObjectSpecification();
+            public Can<ManagedObject> handle(final BulkLoadRequest objectQuery) {
+                var spec = objectQuery.objectSpecification();
                 var entityFacet = spec.entityFacetElseFail();
-                var entities = entityFacet.fetchByQuery(objectQuery.getQuery());
+                var entities = entityFacet.fetchByQuery(objectQuery.query());
                 return entities;
             }
         },
         LoadOther {
             @Override
-            public boolean isHandling(final ObjectBulkLoader.Request objectQuery) {
+            public boolean isHandling(final BulkLoadRequest objectQuery) {
                 return true; // the last handler in the chain
             }
 
             @Override
-            public Can<ManagedObject> handle(final ObjectBulkLoader.Request objectQuery) {
+            public Can<ManagedObject> handle(final BulkLoadRequest objectQuery) {
                 // unknown object load request
                 throw _Exceptions.illegalArgument(
                         "unknown bulk object load request, loader: %s loading ObjectSpecification %s",
-                            this.getClass().getName(), objectQuery.getObjectSpecification());
+                            this.getClass().getName(), objectQuery.objectSpecification());
             }
         }
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java
index 8b221ceb645..79c121960a9 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectCreator.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
 import java.lang.reflect.Modifier;
 
 import org.apache.causeway.applib.spec.Specification;
+import org.apache.causeway.commons.internal.base._Lazy;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.commons.internal.factory._InstanceUtil;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
@@ -30,88 +31,62 @@ import org.apache.causeway.core.metamodel.services.objectlifecycle.ObjectLifecyc
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.spec.feature.MixedIn;
 
-import lombok.AccessLevel;
-import lombok.Getter;
 import lombok.NonNull;
-import lombok.Value;
 import lombok.extern.log4j.Log4j2;
 
 /**
  * Handles injection and lifecycle callbacks.
- *
- * @since 2.0
  */
-interface ObjectCreator {
-
-    ManagedObject createObject(ObjectSpecification objectSpecification);
-
-    // -- FACTORY
-
-    public static ObjectCreator createDefault(final MetaModelContext metaModelContext) {
-        return new DefaultCreationHandler(metaModelContext);
+@Log4j2
+record ObjectCreator(
+        @NonNull MetaModelContext mmc,
+        _Lazy<ObjectLifecyclePublisher> persistenceLifecyclePublisher) {
+
+    ObjectCreator(final MetaModelContext mmc) {
+        this(mmc, _Lazy.threadSafe(()->mmc.getServiceRegistry()
+                .lookupServiceElseFail(ObjectLifecyclePublisher.class)));
     }
 
-    @Value @Log4j2
-    public static class DefaultCreationHandler implements ObjectCreator {
-
-        @Getter
-        private final @NonNull MetaModelContext metaModelContext;
+    ManagedObject createObject(final ObjectSpecification spec) {
+        log.debug("creating instance of {}", spec);
 
-        @Getter(lazy = true, value = AccessLevel.PRIVATE)
-        private final ObjectLifecyclePublisher persistenceLifecyclePublisher =
-            getMetaModelContext().getServiceRegistry()
-                    .lookupServiceElseFail(ObjectLifecyclePublisher.class);
-
-        @Override
-        public ManagedObject createObject(final ObjectSpecification spec) {
-
-            if (log.isDebugEnabled()) {
-                log.debug("creating instance of {}", spec);
-            }
-
-            var pojo = instantiate(spec); // can only be a scalar
-            if(Specification.class.isAssignableFrom(spec.getCorrespondingClass())
-                    || !spec.isValue()) {
-                spec.getServiceInjector().injectServicesInto(pojo);
-            }
-            var domainObject = ManagedObject.adaptSingular(spec, pojo);
+        var pojo = instantiate(spec); // can only be a scalar
+        if(Specification.class.isAssignableFrom(spec.getCorrespondingClass())
+                || !spec.isValue()) {
+            spec.getServiceInjector().injectServicesInto(pojo);
+        }
+        var domainObject = ManagedObject.adaptSingular(spec, pojo);
 
-            // initialize new object
-            domainObject.getSpecification().streamAssociations(MixedIn.EXCLUDED)
+        // initialize new object
+        domainObject.getSpecification().streamAssociations(MixedIn.EXCLUDED)
             .forEach(field->field.toDefault(domainObject));
 
-            if (domainObject.getSpecification().isEntity()) {
-                getPersistenceLifecyclePublisher().onPostCreate(domainObject);
-            }
-
-            return domainObject;
+        if (domainObject.getSpecification().isEntity()) {
+            persistenceLifecyclePublisher().get().onPostCreate(domainObject);
         }
 
-        //  -- HELPER
-
-        private Object instantiate(final ObjectSpecification spec) {
-
-            var type = spec.getCorrespondingClass();
-            if (type.isArray()) {
-                return Array.newInstance(type.getComponentType(), 0);
-            }
-
-            if (Modifier.isAbstract(type.getModifiers())) {
-                throw _Exceptions.unrecoverable("Cannot create an instance of an abstract class: " + type);
-            }
-
-            try {
+        return domainObject;
+    }
 
-                var newInstance = _InstanceUtil.createInstance(type);
-                return newInstance;
+    //  -- HELPER
 
-            } catch (Exception  e) {
-                throw _Exceptions.unrecoverable(e,
-                        "Failed to create instance of type %s", spec.getFullIdentifier());
-            }
+    private Object instantiate(final ObjectSpecification spec) {
+        var type = spec.getCorrespondingClass();
+        if (type.isArray()) {
+            return Array.newInstance(type.getComponentType(), 0);
+        }
 
+        if (Modifier.isAbstract(type.getModifiers())) {
+            throw _Exceptions.unrecoverable("Cannot create an instance of an abstract class: " + type);
         }
 
+        try {
+            var newInstance = _InstanceUtil.createInstance(type);
+            return newInstance;
+        } catch (Exception  e) {
+            throw _Exceptions.unrecoverable(e,
+                    "Failed to create instance of type %s", spec.getFullIdentifier());
+        }
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectLoader.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectLoader.java
index 3e4581bddce..862e17199f2 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectLoader.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectLoader.java
@@ -18,49 +18,30 @@
  */
 package org.apache.causeway.core.metamodel.objectmanager;
 
-import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
 
 import org.apache.causeway.commons.handler.ChainOfResponsibility;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
 import org.apache.causeway.commons.internal.ioc._SingletonBeanProvider;
-import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.facets.object.value.ValueSerializer.Format;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ProtoObject;
 
 import lombok.NonNull;
 
-/**
- * @since 2.0
- */
-public interface ObjectLoader {
-
-    ManagedObject loadObject(@NonNull ProtoObject objectLoadRequest);
-
-    // -- HANDLER
+record ObjectLoader(ChainOfResponsibility<ProtoObject, ManagedObject> chain) {
 
-    static interface Handler
-    extends
-        ChainOfResponsibility.Handler<ProtoObject, ManagedObject> {
+    ObjectLoader() {
+        this(new ChainOfResponsibility<>("ObjectLoader", BuiltinHandlers.values()));
     }
 
-    // -- FACTORY
-
-    public static ObjectLoader createDefault(final MetaModelContext mmc) {
-        return request ->
-            ChainOfResponsibility.named(
-                    "ObjectLoader",
-                    handlers)
-                .handle(Objects.requireNonNull(request));
+    ManagedObject loadObject(@NonNull final ProtoObject objectLoadRequest) {
+        return chain.handle(objectLoadRequest);
     }
 
     // -- HANDLERS
 
-    static final List<Handler> handlers = List.of(BuiltinHandlers.values());
-
-    enum BuiltinHandlers implements Handler {
+    enum BuiltinHandlers implements ChainOfResponsibility.Handler<ProtoObject, ManagedObject> {
         LoadService{
             @Override
             public boolean isHandling(final ProtoObject objectLoadRequest) {
@@ -144,8 +125,7 @@ public interface ObjectLoader {
                         "None of the registered ObjectLoaders knows how to load this object. (loader: %s loading %s)",
                             this.getClass().getName(), objectLoadRequest);
             }
-        },
-        ;
+        };
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManager.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManager.java
index 4002d3d6352..554fdc7feb7 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManager.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManager.java
@@ -21,20 +21,29 @@ package org.apache.causeway.core.metamodel.objectmanager;
 import java.util.Optional;
 import java.util.function.Supplier;
 
+import jakarta.inject.Named;
+
 import org.springframework.lang.Nullable;
 
 import org.apache.causeway.applib.exceptions.unrecoverable.BookmarkNotFoundException;
+import org.apache.causeway.applib.query.Query;
 import org.apache.causeway.applib.services.bookmark.Bookmark;
 import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.annotations.BeanInternal;
 import org.apache.causeway.commons.internal.assertions._Assert;
 import org.apache.causeway.commons.internal.base._NullSafe;
 import org.apache.causeway.commons.internal.exceptions._Exceptions;
+import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
 import org.apache.causeway.core.metamodel.context.HasMetaModelContext;
+import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
 import org.apache.causeway.core.metamodel.object.PackedManagedObject;
 import org.apache.causeway.core.metamodel.object.ProtoObject;
 import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMemento;
+import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMementoCollection;
+import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMementoForEmpty;
+import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMementoForScalar;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectActionParameter;
 import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
@@ -50,41 +59,36 @@ import lombok.NonNull;
  *
  * @since 2.0
  */
-public interface ObjectManager extends HasMetaModelContext {
-
-    ObjectCreator getObjectCreator();
-    ObjectLoader getObjectLoader();
-    ObjectBulkLoader getObjectBulkLoader();
-
-    // -- OBJECT MEMENTOS
+@SuppressWarnings("exports")
+@BeanInternal
+@Named(CausewayModuleCoreMetamodel.NAMESPACE + ".ObjectManager")
+public record ObjectManager(
+        MetaModelContext mmc,
+        ObjectCreator objectCreator,
+        ObjectLoader objectLoader,
+        ObjectBulkLoader objectBulkLoader) implements HasMetaModelContext {
 
-    default Optional<ObjectMemento> mementify(final @Nullable ManagedObject object) {
-        return Optional.ofNullable(object)
-        .flatMap(ManagedObject::getMemento);
-    }
+    // -- REQUEST (VALUE) TYPE
 
-    default ObjectMemento mementifyElseFail(final @NonNull ManagedObject object) {
-        return object.getMemento()
-                .orElseThrow(()->
-                    _Exceptions.unrecoverable("failed to create memento for  %s", object.getSpecification()));
+    public record BulkLoadRequest(
+        ObjectSpecification objectSpecification,
+        Query<?> query) {
     }
 
-    //TODO why not use loadObject(bookmark) instead
-    ManagedObject demementify(final ObjectMemento memento);
-    //TODO why not use loadObject(bookmark) instead
-    default ManagedObject demementify(final ObjectSpecification spec, final ObjectMemento memento) {
-        return demementify(memento);
+    public ObjectManager(final MetaModelContext mmc) {
+        this(mmc,
+                new ObjectCreator(mmc),
+                new ObjectLoader(),
+                new ObjectBulkLoader());
     }
 
-    // -- SHORTCUTS
-
     /**
      * Creates and initializes an instance conforming to given request parameters.
      * <p>
      * Resolves injection-points for the result. (Handles service injection.)
      */
-    public default ManagedObject createObject(final ObjectSpecification objectCreateRequest) {
-        return getObjectCreator().createObject(objectCreateRequest);
+    public ManagedObject createObject(final ObjectSpecification objectCreateRequest) {
+        return objectCreator().createObject(objectCreateRequest);
     }
 
     /**
@@ -92,8 +96,8 @@ public interface ObjectManager extends HasMetaModelContext {
      * <p>
      * Resolves injection-points for the result. (Handles service injection.)
      */
-    public default ManagedObject loadObject(final ProtoObject objectLoadRequest) {
-        return getObjectLoader().loadObject(objectLoadRequest);
+    public ManagedObject loadObject(final ProtoObject objectLoadRequest) {
+        return objectLoader().loadObject(objectLoadRequest);
     }
 
     /**
@@ -103,7 +107,7 @@ public interface ObjectManager extends HasMetaModelContext {
      * <p>
      * Supports alias lookup.
      */
-    default Optional<ManagedObject> loadObject(final @Nullable Bookmark bookmark) {
+    public Optional<ManagedObject> loadObject(final @Nullable Bookmark bookmark) {
         if(bookmark==null) {
             return Optional.empty();
         }
@@ -118,7 +122,7 @@ public interface ObjectManager extends HasMetaModelContext {
      * Does NOT handle {@link PackedManagedObject}. (Needs to be handled by the caller.)
      * @see #debookmark(Bookmark)
      */
-    default Bookmark bookmark(final @NonNull ManagedObject managedObj) {
+    public Bookmark bookmark(final @NonNull ManagedObject managedObj) {
         return ManagedObjects.bookmark(managedObj)
                 .orElseGet(()->Bookmark.empty(managedObj.getLogicalType()));
     }
@@ -128,7 +132,7 @@ public interface ObjectManager extends HasMetaModelContext {
      * Does NOT handle {@link PackedManagedObject}. (Needs to be handled by the caller.)
      * @see #bookmark(ManagedObject)
      */
-    default ManagedObject debookmark(final @NonNull Bookmark bookmark) {
+    public ManagedObject debookmark(final @NonNull Bookmark bookmark) {
         return bookmark.isEmpty()
             ? ManagedObject.empty(getSpecificationLoader().specForBookmarkElseFail(bookmark))
             : loadObjectElseFail(bookmark);
@@ -137,7 +141,7 @@ public interface ObjectManager extends HasMetaModelContext {
     /**
      * @see #loadObject(Bookmark)
      */
-    default ManagedObject loadObjectElseFail(final @NonNull Bookmark bookmark) {
+    public ManagedObject loadObjectElseFail(final @NonNull Bookmark bookmark) {
         var adapter = loadObject(bookmark)
                 .orElseThrow(() -> new BookmarkNotFoundException(String.format("Bookmark %s was not found.", bookmark)));
         if(adapter.getSpecialization().isEntity()) {
@@ -150,11 +154,11 @@ public interface ObjectManager extends HasMetaModelContext {
     /**
      * Resolves injection-points for the result. (Handles service injection.)
      */
-    public default Can<ManagedObject> queryObjects(final ObjectBulkLoader.Request objectQuery) {
-        return getObjectBulkLoader().loadObject(objectQuery);
+    public Can<ManagedObject> queryObjects(final BulkLoadRequest objectQuery) {
+        return objectBulkLoader().loadObject(objectQuery);
     }
 
-    public default Optional<ObjectSpecification> specForPojo(final @Nullable Object pojo) {
+    public Optional<ObjectSpecification> specForPojo(final @Nullable Object pojo) {
         if(pojo==null) {
             return Optional.empty();
         }
@@ -162,7 +166,7 @@ public interface ObjectManager extends HasMetaModelContext {
     }
 
     @Override
-    default Optional<ObjectSpecification> specForType(final @Nullable Class<?> domainType) {
+    public Optional<ObjectSpecification> specForType(final @Nullable Class<?> domainType) {
         return getMetaModelContext().getSpecificationLoader().specForType(domainType);
     }
 
@@ -181,7 +185,7 @@ public interface ObjectManager extends HasMetaModelContext {
      * @see ManagedObject#adaptParameter(ObjectActionParameter, Object)
      * @see ManagedObject#adaptProperty(OneToOneAssociation, Object)
      */
-    public default ManagedObject adapt(final @Nullable Object pojo) {
+    public ManagedObject adapt(final @Nullable Object pojo) {
         return adapt(pojo, ()->specForType(Object.class).orElseThrow());
     }
 
@@ -191,7 +195,7 @@ public interface ObjectManager extends HasMetaModelContext {
      * <p>
      * Resolves injection-points for the result. (Handles service injection.)
      */
-    public default ManagedObject adapt(
+    public ManagedObject adapt(
             final @Nullable Object pojo,
             final @NonNull Supplier<ObjectSpecification> fallbackElementType) {
         if(pojo==null) {
@@ -221,4 +225,69 @@ public interface ObjectManager extends HasMetaModelContext {
                         .collect(Can.toCan()));
     }
 
+    // -- OBJECT MEMENTOS
+
+    public Optional<ObjectMemento> mementify(final @Nullable ManagedObject object) {
+        return Optional.ofNullable(object)
+        .flatMap(ManagedObject::getMemento);
+    }
+
+    public ObjectMemento mementifyElseFail(final @NonNull ManagedObject object) {
+        return object.getMemento()
+                .orElseThrow(()->
+                    _Exceptions.unrecoverable("failed to create memento for  %s", object.getSpecification()));
+    }
+
+
+    //TODO why not use loadObject(bookmark) instead
+    public ManagedObject demementify(final ObjectSpecification spec, final ObjectMemento memento) {
+        return demementify(memento);
+    }
+
+    //TODO why not use loadObject(bookmark) instead
+    public ManagedObject demementify(final @Nullable ObjectMemento memento) {
+
+        if(memento==null) {
+            return null;
+        }
+
+        if(memento instanceof ObjectMementoForEmpty) {
+            var objectMementoForEmpty = (ObjectMementoForEmpty) memento;
+            var logicalType = objectMementoForEmpty.getLogicalType();
+            /* note: we recover from (corresponding) class not logical-type-name,
+             * as the latter can be ambiguous, when shared in a type hierarchy*/
+            var spec = getSpecificationLoader().specForLogicalType(logicalType);
+            return spec.isPresent()
+                    ? ManagedObject.empty(spec.get())
+                    : ManagedObject.unspecified();
+        }
+
+        if(memento instanceof ObjectMementoCollection) {
+            var objectMementoCollection = (ObjectMementoCollection) memento;
+
+            var logicalType = objectMementoCollection.getLogicalType();
+            /* note: we recover from (corresponding) class not logical-type-name,
+             * as the latter can be ambiguous, when shared in a type hierarchy*/
+            var elementSpec = getSpecificationLoader().specForLogicalTypeElseFail(logicalType);
+
+            var objects = objectMementoCollection.unwrapList().stream()
+                    .map(this::demementify)
+                    .collect(Can.toCan());
+
+            return ManagedObject.packed(elementSpec, objects);
+        }
+
+        if(memento instanceof ObjectMementoForScalar) {
+            var objectMementoAdapter = (ObjectMementoForScalar) memento;
+            return objectMementoAdapter.reconstructObject(getMetaModelContext());
+        }
+
+        throw _Exceptions.unrecoverable("unsupported ObjectMemento type %s", memento.getClass());
+    }
+
+    @Override
+    public MetaModelContext getMetaModelContext() {
+        return mmc;
+    }
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManagerDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManagerDefault.java
deleted file mode 100644
index a7222b03e64..00000000000
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/objectmanager/ObjectManagerDefault.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.causeway.core.metamodel.objectmanager;
-
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.Priority;
-import jakarta.inject.Inject;
-import jakarta.inject.Named;
-
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.lang.Nullable;
-import org.springframework.stereotype.Service;
-
-import org.apache.causeway.applib.annotation.PriorityPrecedence;
-import org.apache.causeway.commons.collections.Can;
-import org.apache.causeway.commons.internal.exceptions._Exceptions;
-import org.apache.causeway.core.metamodel.CausewayModuleCoreMetamodel;
-import org.apache.causeway.core.metamodel.context.MetaModelContext;
-import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMemento;
-import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMementoCollection;
-import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMementoForEmpty;
-import org.apache.causeway.core.metamodel.objectmanager.memento.ObjectMementoForScalar;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-/**
- * @since 2.0
- */
-@Service
-@Named(CausewayModuleCoreMetamodel.NAMESPACE + ".ObjectManagerDefault")
-@Priority(PriorityPrecedence.EARLY)
-@Qualifier("DEFAULT")
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
-public class ObjectManagerDefault implements ObjectManager {
-
-    @Getter(onMethod_ = {@Override})
-    private final MetaModelContext metaModelContext;
-
-    @Getter(onMethod_ = {@Override}) private ObjectLoader objectLoader;
-    @Getter(onMethod_ = {@Override}) private ObjectBulkLoader objectBulkLoader;
-    @Getter(onMethod_ = {@Override}) private ObjectCreator objectCreator;
-
-    @PostConstruct
-    public void init() {
-        objectCreator = ObjectCreator.createDefault(metaModelContext);
-        objectLoader = ObjectLoader.createDefault(metaModelContext);
-        objectBulkLoader = ObjectBulkLoader.createDefault(metaModelContext);
-    }
-
-    @Override
-    public ManagedObject demementify(final @Nullable ObjectMemento memento) {
-
-        if(memento==null) {
-            return null;
-        }
-
-        if(memento instanceof ObjectMementoForEmpty) {
-            var objectMementoForEmpty = (ObjectMementoForEmpty) memento;
-            var logicalType = objectMementoForEmpty.getLogicalType();
-            /* note: we recover from (corresponding) class not logical-type-name,
-             * as the latter can be ambiguous, when shared in a type hierarchy*/
-            var spec = getSpecificationLoader().specForLogicalType(logicalType);
-            return spec.isPresent()
-                    ? ManagedObject.empty(spec.get())
-                    : ManagedObject.unspecified();
-        }
-
-        if(memento instanceof ObjectMementoCollection) {
-            var objectMementoCollection = (ObjectMementoCollection) memento;
-
-            var logicalType = objectMementoCollection.getLogicalType();
-            /* note: we recover from (corresponding) class not logical-type-name,
-             * as the latter can be ambiguous, when shared in a type hierarchy*/
-            var elementSpec = getSpecificationLoader().specForLogicalTypeElseFail(logicalType);
-
-            var objects = objectMementoCollection.unwrapList().stream()
-                    .map(this::demementify)
-                    .collect(Can.toCan());
-
-            return ManagedObject.packed(elementSpec, objects);
-        }
-
-        if(memento instanceof ObjectMementoForScalar) {
-            var objectMementoAdapter = (ObjectMementoForScalar) memento;
-            return objectMementoAdapter.reconstructObject(getMetaModelContext());
-        }
-
-        throw _Exceptions.unrecoverable("unsupported ObjectMemento type %s", memento.getClass());
-    }
-
-    // JUnit support
-    public static ObjectManager forTesting(final MetaModelContext metaModelContext) {
-        var objectManager = new ObjectManagerDefault(metaModelContext);
-        objectManager.init();
-        return objectManager;
-    }
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java
index 3d7fa9f02ad..586326dd2e1 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java
@@ -42,8 +42,8 @@ import org.apache.causeway.commons.tabular.TabularModel.TabularSheet;
 import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
-import org.apache.causeway.core.metamodel.objectmanager.ObjectBulkLoader;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
+import org.apache.causeway.core.metamodel.objectmanager.ObjectManager.BulkLoadRequest;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.spec.feature.MixedIn;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectAssociation;
@@ -249,7 +249,7 @@ public class DataTable implements Serializable {
                             requestType,
                             resultType));
         }
-        var queryRequest = ObjectBulkLoader.Request.of(getElementType(), query);
+        var queryRequest = new BulkLoadRequest(getElementType(), query);
         var allMatching = getElementType().getObjectManager().queryObjects(queryRequest);
         return setDataElements(allMatching);
     }
diff --git a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/repository/RepositoryServiceDefault.java b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/repository/RepositoryServiceDefault.java
index d4579bd6a81..cfe4e8a5042 100644
--- a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/repository/RepositoryServiceDefault.java
+++ b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/repository/RepositoryServiceDefault.java
@@ -52,7 +52,7 @@ import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.object.ManagedObjects;
 import org.apache.causeway.core.metamodel.object.MmEntityUtils;
 import org.apache.causeway.core.metamodel.object.MmUnwrapUtils;
-import org.apache.causeway.core.metamodel.objectmanager.ObjectBulkLoader;
+import org.apache.causeway.core.metamodel.objectmanager.ObjectManager.BulkLoadRequest;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.runtime.flushmgmt.FlushMgmt;
 import org.apache.causeway.persistence.commons.CausewayModulePersistenceCommons;
@@ -111,7 +111,7 @@ implements RepositoryService, HasMetaModelContext {
 
     @SneakyThrows
     @Override
-    public <T> T execInBulk(Callable<T> callable) {
+    public <T> T execInBulk(final Callable<T> callable) {
         try {
             suppressFlushing();
             return callable.call();
@@ -216,7 +216,7 @@ implements RepositoryService, HasMetaModelContext {
             return Collections.emptyList();
         }
 
-        var queryRequest = ObjectBulkLoader.Request.of(resultTypeSpec, query);
+        var queryRequest = new BulkLoadRequest(resultTypeSpec, query);
         var allMatching = getObjectManager().queryObjects(queryRequest);
         final List<T> resultList = _Casts.uncheckedCast(MmUnwrapUtils.multipleAsList(allMatching));
         return resultList;