You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2020/09/19 13:34:37 UTC

[isis] 02/03: ISIS-2222: reworking Command and other stuff

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

danhaywood pushed a commit to branch ISIS-2222
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 1b3db60272e34b0ec8911a8c24e65639e601e8ab
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Sep 14 15:49:29 2020 +0100

     ISIS-2222: reworking Command and other stuff
---
 .../org/apache/isis/applib/IsisModuleApplib.java   |   4 +-
 .../org/apache/isis/applib/annotation/Action.java  |   6 +-
 .../apache/isis/applib/annotation/Property.java    |   6 +-
 .../isis/applib/services/command/Command.java      |   3 +-
 .../applib/services/commanddto/HasCommandDto.java  |  12 ++
 .../conmap/ContentMappingServiceForCommandDto.java | 106 ++++++++++++++
 .../ContentMappingServiceForCommandsDto.java       |   2 +-
 .../conmap}/UserDataKeys.java                      |   2 +-
 .../processor}/CommandDtoProcessor.java            |  33 +++--
 .../CommandDtoProcessorForActionAbstract.java      |   6 +-
 .../CommandDtoProcessorForPropertyAbstract.java    |   6 +-
 .../processor}/spi/CommandDtoProcessorService.java |  19 ++-
 .../ContentMappingServiceForCommandDto.java        | 153 ---------------------
 .../services/metamodel/MetaModelService.java       |   6 +-
 .../apache/isis/core/config/IsisConfiguration.java |  90 ++++++------
 .../config/application-secondary.properties        |   7 +-
 examples/demo/web/pom.xml                          |   6 +-
 .../src/main/java/demoapp/web/DemoAppManifest.java |   8 +-
 .../wicket/src/main/resources/log4j2-spring.xml    |   2 +-
 .../extensions/commandlog/impl/jdo/CommandJdo.java |   9 +-
 .../commandlog/impl/jdo/CommandJdoRepository.java  |   6 +-
 .../commandreplay/impl/fetch/PrimaryConfig.java    |  38 -----
 .../commandreplay/impl/fetch/SecondaryConfig.java  |  32 -----
 .../impl/mixins/CommandJdo_openOnPrimary.java      |  57 --------
 .../impl/mixins/CommandJdo_replayQueue.java        |  47 -------
 extensions/core/command-replay/pom.xml             |   3 +-
 .../core/command-replay/{impl => primary}/pom.xml  |  16 +--
 .../primary/IsisModuleExtCommandReplayPrimary.java |  33 +++++
 .../primary}/mixins/CommandJdo_download.java       |  27 ++--
 .../primary/spiimpl/CaptureResultOfCommand.java    |  62 +++++++++
 .../primary}/ui/CommandReplayOnPrimaryService.java |  39 ++++--
 .../command-replay/{impl => secondary}/pom.xml     |   8 +-
 .../IsisModuleExtCommandReplaySecondary.java}      |  34 ++---
 .../commandreplay/secondary}/SecondaryStatus.java  |   2 +-
 .../commandreplay/secondary}/StatusException.java  |   2 +-
 .../secondary/analyser}/CommandReplayAnalyser.java |   2 +-
 .../analyser}/CommandReplayAnalyserException.java  |  17 ++-
 .../analyser}/CommandReplayAnalyserResult.java     |  17 ++-
 .../analysis/CommandReplayAnalysisService.java     |  12 +-
 .../secondary}/clock/TickingClockService.java      |  33 +++--
 .../secondary/config/SecondaryConfig.java          |  54 ++++++++
 .../executor/CommandExecutorServiceWithTime.java   |  22 +--
 .../secondary}/fetch/CommandFetcher.java           |  50 ++++---
 .../secondary}/job/ReplicateAndReplayJob.java      |  16 +--
 .../secondary}/job/SecondaryStatusData.java        |   4 +-
 .../jobcallables}/IsTickingClockInitialized.java   |   4 +-
 .../jobcallables}/ReplicateAndRunCommands.java     |  96 +++++++------
 .../secondary}/mixins/CommandJdo_exclude.java      |  23 ++--
 .../secondary/mixins/CommandJdo_openOnPrimary.java |  54 ++++++++
 .../secondary}/mixins/CommandJdo_replayNext.java   |  45 +++---
 .../secondary/mixins/CommandJdo_replayQueue.java   |  42 ++++++
 .../spi/ReplayCommandExecutionController.java      |   2 +-
 .../ui/CommandReplayOnSecondaryService.java        |  23 ++--
 .../secondary/fetch/CommandFetcher_Test.java}      |   8 +-
 extensions/pom.xml                                 |   8 +-
 .../impl/client/JaxRsClientDefault.java            |   9 +-
 .../viewer/resources/ObjectActionArgHelper.java    |  10 +-
 57 files changed, 776 insertions(+), 667 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
index c68a0b5..fd7732e 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
@@ -36,8 +36,8 @@ import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.applib.services.command.CommandService;
 import org.apache.isis.applib.services.confview.ConfigurationMenu;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandDto;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandsDto;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandDto;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandsDto;
 import org.apache.isis.applib.services.iactn.InteractionContext;
 import org.apache.isis.applib.services.jaxb.JaxbServiceDefault;
 import org.apache.isis.applib.services.layout.LayoutServiceMenu;
diff --git a/api/applib/src/main/java/org/apache/isis/applib/annotation/Action.java b/api/applib/src/main/java/org/apache/isis/applib/annotation/Action.java
index 12583cd..f8c0703 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/annotation/Action.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/annotation/Action.java
@@ -26,10 +26,10 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 import org.apache.isis.applib.events.domain.ActionDomainEvent;
-import org.apache.isis.applib.services.command.CommandDtoProcessor;
+import org.apache.isis.applib.services.commanddto.processor.CommandDtoProcessor;
 import org.apache.isis.applib.services.command.CommandService;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandDto;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandsDto;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandDto;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandsDto;
 import org.apache.isis.applib.value.Blob;
 import org.apache.isis.applib.value.Clob;
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/annotation/Property.java b/api/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
index 5393e2c..65db374 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
@@ -26,9 +26,9 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
 import org.apache.isis.applib.events.domain.PropertyDomainEvent;
-import org.apache.isis.applib.services.command.CommandDtoProcessor;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandDto;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandsDto;
+import org.apache.isis.applib.services.commanddto.processor.CommandDtoProcessor;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandDto;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandsDto;
 import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.applib.value.Blob;
 import org.apache.isis.applib.value.Clob;
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/command/Command.java b/api/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
index 5967960..c7e11e4 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/command/Command.java
@@ -25,6 +25,7 @@ import org.apache.isis.applib.events.domain.ActionDomainEvent;
 import org.apache.isis.applib.services.HasUniqueId;
 import org.apache.isis.applib.services.HasUsername;
 import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.commanddto.HasCommandDto;
 import org.apache.isis.applib.services.iactn.Interaction;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.applib.services.wrapper.control.AsyncControl;
@@ -67,7 +68,7 @@ import lombok.Getter;
  * </p>
  */
 // tag::refguide[]
-public class Command implements HasUniqueId, HasUsername {
+public class Command implements HasUniqueId, HasUsername, HasCommandDto {
 
     public Command() {
         this(UUID.randomUUID());
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/HasCommandDto.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/HasCommandDto.java
new file mode 100644
index 0000000..6c9d631
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/HasCommandDto.java
@@ -0,0 +1,12 @@
+package org.apache.isis.applib.services.commanddto;
+
+import org.apache.isis.schema.cmd.v2.CommandDto;
+
+/**
+ * Objects implementing this interface will be processed automatically by
+ * {@link org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandDto}.
+ */
+public interface HasCommandDto {
+
+    CommandDto getCommandDto();
+}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/ContentMappingServiceForCommandDto.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/ContentMappingServiceForCommandDto.java
new file mode 100644
index 0000000..6e1edfb
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/ContentMappingServiceForCommandDto.java
@@ -0,0 +1,106 @@
+/*
+ *  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.isis.applib.services.commanddto.conmap;
+
+import java.sql.Timestamp;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.ws.rs.core.MediaType;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.commanddto.HasCommandDto;
+import org.apache.isis.applib.services.commanddto.processor.CommandDtoProcessor;
+import org.apache.isis.applib.services.conmap.ContentMappingService;
+import org.apache.isis.applib.services.commanddto.processor.spi.CommandDtoProcessorService;
+import org.apache.isis.applib.services.metamodel.MetaModelService;
+import org.apache.isis.applib.util.schema.CommandDtoUtils;
+import org.apache.isis.core.commons.internal.exceptions._Exceptions;
+import org.apache.isis.schema.cmd.v2.CommandDto;
+import org.apache.isis.schema.common.v2.PeriodDto;
+
+import lombok.val;
+
+@Service
+@Named("isisApplib.ContentMappingServiceForCommandDto")
+@Order(OrderPrecedence.EARLY)
+@Primary
+@Qualifier("CommandDto")
+public class ContentMappingServiceForCommandDto implements ContentMappingService {
+
+    @Override
+    public Object map(Object object, final List<MediaType> acceptableMediaTypes) {
+        final boolean supported = Util.isSupported(CommandDto.class, acceptableMediaTypes);
+        if(!supported) {
+            return null;
+        }
+
+        return asProcessedDto(object);
+    }
+
+    CommandDto asProcessedDto(final Object object) {
+        val commandDto = asCommandDto(object);
+        return asProcessedDto(object, commandDto);
+    }
+
+    private CommandDto asCommandDto(Object object) {
+        if(object instanceof CommandDto) {
+            return (CommandDto) object;
+        }
+        if(object instanceof HasCommandDto) {
+            return ((HasCommandDto) object).getCommandDto();
+        }
+        return null;
+    }
+
+    private CommandDto asProcessedDto(final Object domainObject, CommandDto commandDto) {
+
+        // global processors
+        for (final CommandDtoProcessorService commandDtoProcessorService : commandDtoProcessorServices) {
+            commandDto = commandDtoProcessorService.process(domainObject, commandDto);
+            if(commandDto == null) {
+                // any processor could return null, effectively breaking the chain.
+                return null;
+            }
+        }
+
+        // specific processor for this specific member (action or property)
+        val logicalMemberId = commandDto.getMember().getLogicalMemberIdentifier();
+        final CommandDtoProcessor commandDtoProcessor =
+                metaModelService.commandDtoProcessorFor(logicalMemberId);
+        if (commandDtoProcessor == null) {
+            return commandDto;
+        }
+        return commandDtoProcessor.process(commandDto);
+    }
+
+
+    @Inject MetaModelService metaModelService;
+    @Inject List<CommandDtoProcessorService> commandDtoProcessorServices;
+
+}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/ContentMappingServiceForCommandsDto.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/ContentMappingServiceForCommandsDto.java
similarity index 98%
rename from api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/ContentMappingServiceForCommandsDto.java
rename to api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/ContentMappingServiceForCommandsDto.java
index ade5070..4d491b2 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/ContentMappingServiceForCommandsDto.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/ContentMappingServiceForCommandsDto.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.conmap.command;
+package org.apache.isis.applib.services.commanddto.conmap;
 
 import java.util.List;
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/UserDataKeys.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/UserDataKeys.java
similarity index 95%
rename from api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/UserDataKeys.java
rename to api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/UserDataKeys.java
index bc1bb2f..4fc4e59 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/UserDataKeys.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/conmap/UserDataKeys.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.conmap.command;
+package org.apache.isis.applib.services.commanddto.conmap;
 
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessor.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessor.java
similarity index 57%
rename from api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessor.java
rename to api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessor.java
index 60edc81..a0706ad 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessor.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessor.java
@@ -16,33 +16,42 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.command;
+package org.apache.isis.applib.services.commanddto.processor;
 
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
+/**
+ * Refine (or possibly ignore) a command when replicating from primary
+ * to secondary.
+ */
 // tag::refguide[]
 public interface CommandDtoProcessor {
 
     // end::refguide[]
     /**
-     * Returning <tt>null</tt> means that the command's DTO is effectively excluded from any list.
-     * If replicating from master to slave, this allows commands that can't be replicated to be ignored.
-     * @param command
-     * @param commandDto
+     * The implementation can if necessary refine or alter the
+     * {@link CommandDto} to be replicated from primary to secondary.
+     *
+     * <p>
+     *     That said, the most common use case is to return <code>null</code>,
+     *     which results in the command effectively being ignore.
+     * </p>
+     *
+     * @param commandDto - to be processed
+     * @return <tt>null</tt> means that the command's DTO is effectively
+     *         excluded.
      */
     // tag::refguide[]
-    CommandDto process(final Command command, CommandDto commandDto);   // <.>
+    CommandDto process(CommandDto commandDto);   // <.>
 
     // end::refguide[]
     /**
-     * Convenience implementation to simply indicate that no DTO should be returned for a command,
-     * effectively ignoring it for replay purposes.
+     * Convenience implementation to simply indicate that no DTO should be
+     * returned for a command, effectively ignoring it for replication purposes.
      */
-    public static class Null implements CommandDtoProcessor {
+    class Null implements CommandDtoProcessor {
         @Override
-        public CommandDto process(
-                final Command command,
-                final CommandDto commandDto) {
+        public CommandDto process(final CommandDto commandDto) {
             return null;
         }
     }
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessorForActionAbstract.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessorForActionAbstract.java
similarity index 91%
rename from api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessorForActionAbstract.java
rename to api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessorForActionAbstract.java
index 97c9780..b887323 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessorForActionAbstract.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessorForActionAbstract.java
@@ -16,8 +16,9 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.command;
+package org.apache.isis.applib.services.commanddto.processor;
 
+import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.schema.cmd.v2.ActionDto;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 import org.apache.isis.schema.cmd.v2.ParamDto;
@@ -27,9 +28,6 @@ import org.apache.isis.schema.cmd.v2.ParamsDto;
  * Convenience adapter for command processors for action invocations.
  */
 public abstract class CommandDtoProcessorForActionAbstract implements CommandDtoProcessor {
-    protected CommandDto asDto(final Command command) {
-        return command.getCommandDto();
-    }
     protected ActionDto getActionDto(final CommandDto commandDto) {
         return (ActionDto) commandDto.getMember();
     }
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessorForPropertyAbstract.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessorForPropertyAbstract.java
similarity index 88%
rename from api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessorForPropertyAbstract.java
rename to api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessorForPropertyAbstract.java
index b8e2dd3..e833809 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/command/CommandDtoProcessorForPropertyAbstract.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/CommandDtoProcessorForPropertyAbstract.java
@@ -16,8 +16,9 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.command;
+package org.apache.isis.applib.services.commanddto.processor;
 
+import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 import org.apache.isis.schema.cmd.v2.PropertyDto;
 
@@ -26,9 +27,6 @@ import org.apache.isis.schema.cmd.v2.PropertyDto;
  */
 public abstract class CommandDtoProcessorForPropertyAbstract
 implements CommandDtoProcessor {
-    protected CommandDto asDto(final Command commandWithDto) {
-        return commandWithDto.getCommandDto();
-    }
     protected PropertyDto getPropertyDto(final CommandDto commandDto) {
         return (PropertyDto) commandDto.getMember();
     }
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/spi/CommandDtoProcessorService.java b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/spi/CommandDtoProcessorService.java
similarity index 62%
rename from api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/spi/CommandDtoProcessorService.java
rename to api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/spi/CommandDtoProcessorService.java
index 48439df..12831f9 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/spi/CommandDtoProcessorService.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/commanddto/processor/spi/CommandDtoProcessorService.java
@@ -16,12 +16,12 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.conmap.command.spi;
+package org.apache.isis.applib.services.commanddto.processor.spi;
 
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.command.CommandDtoProcessor;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandDto;
+import javax.annotation.Nullable;
+
+import org.apache.isis.applib.services.commanddto.processor.CommandDtoProcessor;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandDto;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
 /**
@@ -32,7 +32,14 @@ import org.apache.isis.schema.cmd.v2.CommandDto;
 // tag::refguide[]
 public interface CommandDtoProcessorService {
 
-    CommandDto process(final Command command, CommandDto commandDto);
+    /**
+     * @param domainObject - is the target that acts as the source of the
+     *                       {@link CommandDto}.
+     * @param commandDto - is either <code>null</code>, or is passed from a
+     *                     previous implementation for further refinement.
+     * @return
+     */
+    CommandDto process(final Object domainObject, @Nullable final CommandDto commandDto);
 
 }
 // end::refguide[]
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/ContentMappingServiceForCommandDto.java b/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/ContentMappingServiceForCommandDto.java
deleted file mode 100644
index c4579c4..0000000
--- a/api/applib/src/main/java/org/apache/isis/applib/services/conmap/command/ContentMappingServiceForCommandDto.java
+++ /dev/null
@@ -1,153 +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.isis.applib.services.conmap.command;
-
-import java.sql.Timestamp;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.ws.rs.core.MediaType;
-
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.annotation.Primary;
-import org.springframework.core.annotation.Order;
-import org.springframework.stereotype.Service;
-
-import org.apache.isis.applib.annotation.OrderPrecedence;
-import org.apache.isis.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.command.CommandDtoProcessor;
-import org.apache.isis.applib.services.conmap.ContentMappingService;
-import org.apache.isis.applib.services.conmap.command.spi.CommandDtoProcessorService;
-import org.apache.isis.applib.services.metamodel.MetaModelService;
-import org.apache.isis.applib.util.schema.CommandDtoUtils;
-import org.apache.isis.core.commons.internal.exceptions._Exceptions;
-import org.apache.isis.schema.cmd.v2.CommandDto;
-import org.apache.isis.schema.common.v2.PeriodDto;
-
-@Service
-@Named("isisApplib.ContentMappingServiceForCommandDto")
-@Order(OrderPrecedence.EARLY)
-@Primary
-@Qualifier("CommandDto")
-public class ContentMappingServiceForCommandDto implements ContentMappingService {
-
-    @Override
-    public Object map(Object object, final List<MediaType> acceptableMediaTypes) {
-        final boolean supported = Util.isSupported(CommandDto.class, acceptableMediaTypes);
-        if(!supported) {
-            return null;
-        }
-
-        return asProcessedDto(object);
-    }
-
-    /**
-     * Not part of the {@link ContentMappingService} API.
-     */
-    public CommandDto map(final Command command) {
-        return asProcessedDto(command);
-    }
-
-    CommandDto asProcessedDto(final Object object) {
-        if (!(object instanceof Command)) {
-            return null;
-        }
-        final Command command = (Command) object;
-        return asProcessedDto(command);
-    }
-
-    private CommandDto asProcessedDto(final Command command) {
-        if(command == null) {
-            return null;
-        }
-        CommandDto commandDto = command.getCommandDto();
-
-        // global processors
-        for (final CommandDtoProcessorService commandDtoProcessorService : commandDtoProcessorServices) {
-            commandDto = commandDtoProcessorService.process(command, commandDto);
-            if(commandDto == null) {
-                // any processor could return null, effectively breaking the chain.
-                return null;
-            }
-        }
-
-        // specific processors for this specific member (action or property)
-        final CommandDtoProcessor commandDtoProcessor =
-                metaModelService.commandDtoProcessorFor(commandDto.getMember().getLogicalMemberIdentifier());
-        if (commandDtoProcessor == null) {
-            return commandDto;
-        }
-        return commandDtoProcessor.process(command, commandDto);
-    }
-
-
-    /**
-     * Uses the SPI infrastructure to copy over standard properties from {@link Command} to {@link CommandDto}.
-     */
-    @Service
-    @Named("isisApplib.ContentMappingServiceForCommandDto.CopyOverFromCommand")
-    // specify quite a high priority since custom processors will probably want to run after this one
-    // (but can choose to run before if they wish)
-    @Order(OrderPrecedence.EARLY)
-    @Qualifier("Command")
-    public static class CopyOverFromCommand implements CommandDtoProcessorService {
-
-        @Override
-        public CommandDto process(final Command command, CommandDto commandDto) {
-
-            // for some reason this isn't being persisted initially, so patch it in.  TODO: should fix this
-            commandDto.setUser(command.getUsername());
-
-            // the timestamp field was only introduced in v1.4 of cmd.xsd, so there's no guarantee
-            // it will have been populated.  We therefore copy the value in from CommandWithDto entity.
-            if(commandDto.getTimestamp() == null) {
-                final Timestamp timestamp = command.getTimestamp();
-                commandDto.setTimestamp(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(timestamp));
-            }
-
-            final Bookmark result = command.getResult();
-            CommandDtoUtils.setUserData(commandDto,
-                    UserDataKeys.RESULT, result != null ? result.toString() : null);
-            // knowing whether there was an exception is on the master is used to determine whether to
-            // continue when replayed on the slave if an exception occurs there also
-            Throwable exception = command.getException();
-            CommandDtoUtils.setUserData(commandDto,
-                    UserDataKeys.EXCEPTION,
-                        _Exceptions.asStacktrace(exception));
-
-            PeriodDto timings = CommandDtoUtils.timingsFor(commandDto);
-            timings.setStartedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(command.getStartedAt()));
-            timings.setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(command.getCompletedAt()));
-
-            return commandDto;
-        }
-    }
-
-
-    @Inject
-    MetaModelService metaModelService;
-
-    @Inject
-    List<CommandDtoProcessorService> commandDtoProcessorServices;
-
-}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java b/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java
index c0c3c09..03dd90d 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService.java
@@ -26,7 +26,7 @@ import org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties.S
 
 import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.command.CommandDtoProcessor;
+import org.apache.isis.applib.services.commanddto.processor.CommandDtoProcessor;
 import org.apache.isis.core.commons.internal.collections._Sets;
 import org.apache.isis.schema.metamodel.v2.MetamodelDto;
 
@@ -79,7 +79,7 @@ public interface MetaModelService {
     BeanSort sortOf(Bookmark bookmark, Mode mode);      // <.>
 
     CommandDtoProcessor commandDtoProcessorFor(         // <.>
-                            String memberIdentifier);
+                            String logicalMemberIdentifier);
 
     // end::refguide[]
     // tag::refguide-1[]
@@ -92,7 +92,7 @@ public interface MetaModelService {
         STRICT,
         // end::refguide-1[]
         /**
-         * If the {@link #sortOf(Class, Mode) sort of} object type is unknown, then return {@link Sort#UNKNOWN}.
+         * If the {@link #sortOf(Class, Mode) sort of} object type is unknown, then return {@link BeanSort#UNKNOWN}.
          */
         // tag::refguide-1[]
         RELAXED
diff --git a/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java b/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
index ceaf342..e0550c8 100644
--- a/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
+++ b/core/config/src/main/java/org/apache/isis/core/config/IsisConfiguration.java
@@ -40,6 +40,7 @@ import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import javax.activation.DataSource;
+import javax.annotation.RegEx;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.validation.Constraint;
@@ -3055,62 +3056,57 @@ public class IsisConfiguration {
         @Data
         public static class CommandReplay {
 
-            private final Primary primary = new Primary();
+            private final PrimaryAccess primaryAccess = new PrimaryAccess();
             @Data
-            public static class Primary {
-                private Optional<String> baseUrl;               
-                private Optional<String> user;               
-                private Optional<String> password;               
-                private Optional<String> baseUrlEndUser;               
-                private Integer batchSize = 10;               
+            public static class PrimaryAccess {
+                @javax.validation.constraints.Pattern(regexp="^http[s]?://[^:]+?(:\\d+)?.*([^/]+/)$")
+                private Optional<String> baseUrlRestful;
+                private Optional<String> user;
+                private Optional<String> password;
+                @javax.validation.constraints.Pattern(regexp="^http[s]?://[^:]+?(:\\d+)?.*([^/]+/)$")
+                private Optional<String> baseUrlWicket;
             }
-            
-            private final Secondary secondary = new Secondary();
+
+            private Integer batchSize = 10;
+
+            private final QuartzSession quartzSession = new QuartzSession();
             @Data
-            public static class Secondary {
-                
-                private final QuartzSession quartzSession = new QuartzSession();
-                @Data
-                public static class QuartzSession {
-                    /**
-                     * The user that runs the replay session secondary.
-                     */
-                    private String user = "isisModuleExtCommandReplaySecondaryUser";
-                    private List<String> roles = listOf("isisModuleExtCommandReplaySecondaryRole");
-                }
-                
-                private final QuartzReplicateAndReplayJob quartzReplicateAndReplayJob = new QuartzReplicateAndReplayJob();
+            public static class QuartzSession {
+                /**
+                 * The user that runs the replay session secondary.
+                 */
+                private String user = "isisModuleExtCommandReplaySecondaryUser";
+                private List<String> roles = listOf("isisModuleExtCommandReplaySecondaryRole");
+            }
+
+            private final QuartzReplicateAndReplayJob quartzReplicateAndReplayJob = new QuartzReplicateAndReplayJob();
+            @Data
+            public static class QuartzReplicateAndReplayJob {
+                /**
+                 * Number of milliseconds before starting the job.
+                 */
+                private long startDelay = 15000;
+                /**
+                 * Number of milliseconds before running again.
+                 */
+                private long repeatInterval = 10000;
+            }
+
+            private final Analyser analyser = new Analyser();
+            @Data
+            public static class Analyser {
+                private final Result result = new Result();
                 @Data
-                public static class QuartzReplicateAndReplayJob {
-                    /**
-                     * Number of milliseconds before starting the job.
-                     */
-                    private long startDelay = 15000;
-                    /**
-                     * Number of milliseconds before running again.
-                     */
-                    private long repeatInterval = 10000;
+                public static class Result {
+                    private boolean enabled = true;
                 }
-
-                private final Analyser analyser = new Analyser();
+                private final Exception exception = new Exception();
                 @Data
-                public static class Analyser {
-                    private final Result result = new Result();
-                    @Data
-                    public static class Result {
-                        private boolean enabled = true;
-                    }
-                    private final Exception exception = new Exception();
-                    @Data
-                    public static class Exception {
-                        private boolean enabled = true;
-                    }
-
+                public static class Exception {
+                    private boolean enabled = true;
                 }
 
             }
-            
-            
         }
         
 
diff --git a/examples/demo/domain/src/main/resources/config/application-secondary.properties b/examples/demo/domain/src/main/resources/config/application-secondary.properties
index 0b1d756..6c2776b 100644
--- a/examples/demo/domain/src/main/resources/config/application-secondary.properties
+++ b/examples/demo/domain/src/main/resources/config/application-secondary.properties
@@ -1,5 +1,6 @@
-isis.extensions.command-replay.primary.base-url=http://localhost:8080
-isis.extensions.command-replay.primary.user=sven
-isis.extensions.command-replay.primary.password=pass
+isis.extensions.command-replay.primary-access.base-url-restful=http://localhost:8080/restful/
+isis.extensions.command-replay.primary-access.base-url-wicket=http://localhost:8080/wicket/
+isis.extensions.command-replay.primary-access.user=sven
+isis.extensions.command-replay.primary-access.password=pass
 
 server.port=9090
\ No newline at end of file
diff --git a/examples/demo/web/pom.xml b/examples/demo/web/pom.xml
index 6108727..83243b3 100644
--- a/examples/demo/web/pom.xml
+++ b/examples/demo/web/pom.xml
@@ -60,7 +60,11 @@
 		<!-- Extensions -->
 		<dependency>
 			<groupId>org.apache.isis.extensions</groupId>
-			<artifactId>isis-extensions-command-replay-impl</artifactId>
+			<artifactId>isis-extensions-command-replay-primary</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.isis.extensions</groupId>
+			<artifactId>isis-extensions-command-replay-secondary</artifactId>
 		</dependency>
 
 	</dependencies>
diff --git a/examples/demo/web/src/main/java/demoapp/web/DemoAppManifest.java b/examples/demo/web/src/main/java/demoapp/web/DemoAppManifest.java
index d79a04e..0ec690e 100644
--- a/examples/demo/web/src/main/java/demoapp/web/DemoAppManifest.java
+++ b/examples/demo/web/src/main/java/demoapp/web/DemoAppManifest.java
@@ -26,10 +26,9 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
-import org.apache.isis.extensions.commandlog.impl.IsisModuleExtCommandLogImpl;
-import org.apache.isis.extensions.commandreplay.impl.IsisModuleExtCommandReplayImpl;
+import org.apache.isis.extensions.commandreplay.primary.IsisModuleExtCommandReplayPrimary;
+import org.apache.isis.extensions.commandreplay.secondary.IsisModuleExtCommandReplaySecondary;
 import org.apache.isis.extensions.cors.impl.IsisModuleExtCorsImpl;
-import org.apache.isis.extensions.quartz.IsisModuleExtQuartzImpl;
 import org.apache.isis.extensions.secman.encryption.jbcrypt.IsisModuleExtSecmanEncryptionJbcrypt;
 import org.apache.isis.extensions.secman.jdo.IsisModuleExtSecmanPersistenceJdo;
 import org.apache.isis.extensions.secman.model.IsisModuleExtSecmanModel;
@@ -55,7 +54,8 @@ import demoapp.dom._infra.fixtures.DemoFixtureScript;
     DemoModule.class, // shared demo core module
 
     // commands
-    IsisModuleExtCommandReplayImpl.class,
+    IsisModuleExtCommandReplayPrimary.class,
+    IsisModuleExtCommandReplaySecondary.class,
 
     // SECURITY
     IsisModuleSecurityShiro.class,
diff --git a/examples/demo/wicket/src/main/resources/log4j2-spring.xml b/examples/demo/wicket/src/main/resources/log4j2-spring.xml
index 5d9f666..4cdedf1 100644
--- a/examples/demo/wicket/src/main/resources/log4j2-spring.xml
+++ b/examples/demo/wicket/src/main/resources/log4j2-spring.xml
@@ -56,7 +56,7 @@ under the License.
 
 		<!-- debugging -->
 		<Logger name="org.apache.isis.applib.services.command.CommandServiceDefault" level="warn" />
-		<Logger name="org.apache.isis.extensions.commandreplay.impl.job.callables.ReplicateAndRunCommands" level="debug" />
+		<Logger name="org.apache.isis.extensions.commandreplay.secondary.jobcallables.ReplicateAndRunCommands" level="debug" />
 
 		<Root level="info">
 			<AppenderRef ref="Console" />
diff --git a/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdo.java b/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdo.java
index 1ba4381..248a56b 100644
--- a/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdo.java
+++ b/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdo.java
@@ -45,7 +45,8 @@ import org.apache.isis.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
 import org.apache.isis.applib.services.DomainChangeRecord;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.conmap.command.UserDataKeys;
+import org.apache.isis.applib.services.commanddto.HasCommandDto;
+import org.apache.isis.applib.services.commanddto.conmap.UserDataKeys;
 import org.apache.isis.applib.services.tablecol.TableColumnOrderForCollectionTypeAbstract;
 import org.apache.isis.applib.types.MemberIdentifierType;
 import org.apache.isis.applib.util.ObjectContracts;
@@ -186,11 +187,11 @@ import lombok.extern.log4j.Log4j2;
                     + "   && startedAt != null "
                     + "   && completedAt != null "
                     + "ORDER BY this.timestamp ASC"),
-    // most recent replayed command previously replicated from primary to
+    // most recent (replayable) command previously replicated from primary to
     // secondary.  This should always exist except for the very first times
     // (after restored the prod DB to secondary).
     @javax.jdo.annotations.Query(
-            name="findReplayedHwm",
+            name="findReplicatedHwm",
             value="SELECT "
                     + "FROM org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo "
                     + "WHERE (replayState == 'OK' || replayState == 'FAILED') "
@@ -251,7 +252,7 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 @NoArgsConstructor
 public class CommandJdo
-        implements DomainChangeRecord, Comparable<CommandJdo> {
+        implements DomainChangeRecord, Comparable<CommandJdo>, HasCommandDto {
 
     public static class TitleUiEvent extends IsisModuleExtCommandLogImpl.TitleUiEvent<CommandJdo> { }
     public static class IconUiEvent extends IsisModuleExtCommandLogImpl.IconUiEvent<CommandJdo> { }
diff --git a/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdoRepository.java b/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdoRepository.java
index 1f632a7..926335f 100644
--- a/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdoRepository.java
+++ b/extensions/core/command-log/impl/src/main/java/org/apache/isis/extensions/commandlog/impl/jdo/CommandJdoRepository.java
@@ -230,7 +230,7 @@ public class CommandJdoRepository {
      * the primary.
      *
      * @param uniqueId - the identifier of the {@link CommandJdo command} being
-     *                   the replay hwm (using {@link #findReplayedHwm()} on the
+     *                   the replay hwm (using {@link #findReplicatedHwm()} on the
      *                   secondary), or null if no HWM was found there.
      * @param batchSize - to restrict the number returned (so that replay
      *                   commands can be batched).
@@ -301,12 +301,12 @@ public class CommandJdoRepository {
      * production database was restored to the secondary
      * </p>
      */
-    public CommandJdo findReplayedHwm() {
+    public CommandJdo findReplicatedHwm() {
 
         // most recent replayable command, replicated from primary to secondary
         return repositoryService.firstMatch(
                 new QueryDefault<>(
-                        CommandJdo.class, "findReplayedHwm"))
+                        CommandJdo.class, "findReplicatedHwm"))
                 .orElseGet(() -> {
             // otherwise, the most recent completed command, run on the secondary,
             // this corresponds to a command restored from a copy of
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/PrimaryConfig.java b/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/PrimaryConfig.java
deleted file mode 100644
index 81fb315..0000000
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/PrimaryConfig.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.apache.isis.extensions.commandreplay.impl.fetch;
-
-import javax.validation.constraints.NotNull;
-
-import org.springframework.stereotype.Service;
-
-import org.apache.isis.core.config.IsisConfiguration;
-
-import lombok.Getter;
-import lombok.val;
-import lombok.extern.log4j.Log4j2;
-
-@Service
-@Log4j2
-public class PrimaryConfig {
-
-    @Getter final String user;
-    @Getter final String password;
-    @Getter final String baseUrl;
-    @Getter final int batchSize;
-
-    public PrimaryConfig(@NotNull final IsisConfiguration isisConfiguration) {
-        val config = isisConfiguration.getExtensions().getCommandReplay().getPrimary();
-        user = config.getUser().orElse(null);
-        password = config.getPassword().orElse(null);
-        baseUrl = config.getBaseUrl()
-                            .map(x -> !x.endsWith("/") ? x + "/" : x)
-                            .orElse(null);
-        batchSize = config.getBatchSize();
-    }
-
-
-    public boolean isConfigured() {
-        return user != null &&
-               password != null &&
-               baseUrl != null;
-    }
-}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/SecondaryConfig.java b/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/SecondaryConfig.java
deleted file mode 100644
index 4680303..0000000
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/SecondaryConfig.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.apache.isis.extensions.commandreplay.impl.fetch;
-
-import java.util.List;
-
-import javax.validation.constraints.NotNull;
-
-import org.springframework.stereotype.Service;
-
-import org.apache.isis.core.config.IsisConfiguration;
-
-import lombok.Getter;
-import lombok.val;
-import lombok.extern.log4j.Log4j2;
-
-@Service
-@Log4j2
-public class SecondaryConfig {
-
-    @Getter final String user;
-    @Getter final List<String> roles;
-
-    public SecondaryConfig(@NotNull final IsisConfiguration isisConfiguration) {
-        val config = isisConfiguration.getExtensions().getCommandReplay().getSecondary();
-        user = config.getQuartzSession().getUser();
-        roles = config.getQuartzSession().getRoles();
-    }
-
-    public boolean isConfigured() {
-        return user != null &&
-               roles != null;
-    }
-}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_openOnPrimary.java b/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_openOnPrimary.java
deleted file mode 100644
index c5af120..0000000
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_openOnPrimary.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.apache.isis.extensions.commandreplay.impl.mixins;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.inject.Inject;
-
-import org.apache.isis.applib.ApplicationException;
-import org.apache.isis.applib.annotation.Action;
-import org.apache.isis.applib.annotation.MemberOrder;
-import org.apache.isis.applib.annotation.SemanticsOf;
-import org.apache.isis.applib.services.bookmark.BookmarkService;
-import org.apache.isis.core.config.IsisConfiguration;
-import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
-import org.apache.isis.extensions.commandreplay.impl.IsisModuleExtCommandReplayImpl;
-
-@Action(
-        semantics = SemanticsOf.SAFE,
-        domainEvent = CommandJdo_openOnPrimary.ActionDomainEvent.class
-)
-public class CommandJdo_openOnPrimary<T> {
-
-    public static class ActionDomainEvent
-            extends IsisModuleExtCommandReplayImpl.ActionDomainEvent<CommandJdo_openOnPrimary> { }
-
-    private final CommandJdo commandJdo;
-    public CommandJdo_openOnPrimary(CommandJdo commandJdo) {
-        this.commandJdo = commandJdo;
-    }
-
-    @MemberOrder(name = "transactionId", sequence = "1")
-    public URL act() {
-        final String baseUrlPrefix = lookupBaseUrlPrefix();
-        final String urlSuffix = bookmarkService2.bookmarkFor(commandJdo).toString();
-
-        try {
-            return new URL(baseUrlPrefix + urlSuffix);
-        } catch (MalformedURLException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public boolean hideAct() {
-        return lookupBaseUrlPrefix() == null;
-    }
-
-    private String lookupBaseUrlPrefix() {
-        return isisConfiguration.getExtensions().getCommandReplay().getPrimary().getBaseUrlEndUser()
-                .map(x -> !x.endsWith("/") ? x + "/" : x)
-                .map(x -> x + "wicket/entity/")
-                .orElse(null);
-    }
-
-    @Inject IsisConfiguration isisConfiguration;
-    @Inject BookmarkService bookmarkService2;
-
-}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_replayQueue.java b/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_replayQueue.java
deleted file mode 100644
index f4b20b3..0000000
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_replayQueue.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.apache.isis.extensions.commandreplay.impl.mixins;
-
-import java.util.List;
-
-import javax.inject.Inject;
-
-import org.apache.isis.applib.annotation.Collection;
-import org.apache.isis.applib.annotation.CollectionLayout;
-import org.apache.isis.applib.annotation.MemberOrder;
-import org.apache.isis.applib.annotation.Mixin;
-import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
-import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdoRepository;
-import org.apache.isis.extensions.commandreplay.impl.IsisModuleExtCommandReplayImpl;
-import org.apache.isis.extensions.commandreplay.impl.fetch.PrimaryConfig;
-
-@Collection(
-        domainEvent = CommandJdo_replayQueue.CollectionDomainEvent.class
-)
-@CollectionLayout(
-        defaultView = "table"
-)
-@Mixin(method = "coll")
-public class CommandJdo_replayQueue {
-
-    public static class CollectionDomainEvent
-            extends IsisModuleExtCommandReplayImpl.CollectionDomainEvent<CommandJdo_replayQueue, CommandJdo> { }
-
-    private final CommandJdo commandJdo;
-    public CommandJdo_replayQueue(final CommandJdo commandJdo) {
-        this.commandJdo = commandJdo;
-    }
-
-    @MemberOrder(sequence = "100.100")
-    public List<CommandJdo> coll() {
-        return commandJdoRepository.findReplayedOnSecondary();
-    }
-
-    public boolean hideColl() {
-        return !primaryConfig.isConfigured();
-    }
-
-    @Inject
-    PrimaryConfig primaryConfig;
-    @Inject
-    CommandJdoRepository commandJdoRepository;
-
-}
diff --git a/extensions/core/command-replay/pom.xml b/extensions/core/command-replay/pom.xml
index 208215a..8bbfddf 100644
--- a/extensions/core/command-replay/pom.xml
+++ b/extensions/core/command-replay/pom.xml
@@ -56,7 +56,8 @@
 	</dependencyManagement>
 
 	<modules>
-		<module>impl</module>
+		<module>primary</module>
+		<module>secondary</module>
 	</modules>
 
 </project>
diff --git a/extensions/core/command-replay/impl/pom.xml b/extensions/core/command-replay/primary/pom.xml
similarity index 83%
copy from extensions/core/command-replay/impl/pom.xml
copy to extensions/core/command-replay/primary/pom.xml
index 195e92b..45892f0 100644
--- a/extensions/core/command-replay/impl/pom.xml
+++ b/extensions/core/command-replay/primary/pom.xml
@@ -19,19 +19,18 @@
         <version>2.0.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>isis-extensions-command-replay-impl</artifactId>
-    <name>Apache Isis Ext - Command Replay Implementation</name>
+    <artifactId>isis-extensions-command-replay-primary</artifactId>
+    <name>Apache Isis Ext - Command Replay for Primary</name>
 
     <packaging>jar</packaging>
 
     <description>
-        A module providing a Quartz Job to run on a secondary system,
-		for obtaining commands from a primary and saving them so that they are replayed.
+        A module for obtaining commands from a primary
     </description>
 
     <properties>
-        <jar-plugin.automaticModuleName>org.apache.isis.extensions.commandreplay.impl</jar-plugin.automaticModuleName>
-        <git-plugin.propertiesDir>org/apache/isis/extensions/commandreplay</git-plugin.propertiesDir>
+        <jar-plugin.automaticModuleName>org.apache.isis.extensions.commandreplay.primary</jar-plugin.automaticModuleName>
+        <git-plugin.propertiesDir>org/apache/isis/extensions/commandreplay/primary</git-plugin.propertiesDir>
     </properties>
 
     <dependencies>
@@ -70,11 +69,6 @@
             <artifactId>isis-extensions-command-log-impl</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>org.apache.isis.extensions</groupId>
-            <artifactId>isis-extensions-quartz-impl</artifactId>
-        </dependency>
-
     </dependencies>
 
 </project>
diff --git a/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/IsisModuleExtCommandReplayPrimary.java b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/IsisModuleExtCommandReplayPrimary.java
new file mode 100644
index 0000000..5086b15
--- /dev/null
+++ b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/IsisModuleExtCommandReplayPrimary.java
@@ -0,0 +1,33 @@
+package org.apache.isis.extensions.commandreplay.primary;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Profile;
+
+import org.apache.isis.extensions.commandlog.impl.IsisModuleExtCommandLogImpl;
+import org.apache.isis.extensions.commandreplay.primary.spiimpl.CaptureResultOfCommand;
+import org.apache.isis.extensions.commandreplay.primary.ui.CommandReplayOnPrimaryService;
+
+@Configuration
+@Import({
+        // @Configuration's
+        IsisModuleExtCommandLogImpl.class,
+
+        // @Service's
+        CommandReplayOnPrimaryService.class,
+        CaptureResultOfCommand.class,
+
+})
+@Profile("primary")
+public class IsisModuleExtCommandReplayPrimary {
+
+    public abstract static class ActionDomainEvent<S>
+            extends org.apache.isis.applib.events.domain.ActionDomainEvent<S> { }
+
+    public abstract static class CollectionDomainEvent<S,T>
+            extends org.apache.isis.applib.events.domain.CollectionDomainEvent<S,T> { }
+
+    public abstract static class PropertyDomainEvent<S,T>
+            extends org.apache.isis.applib.events.domain.PropertyDomainEvent<S,T> { }
+
+}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_download.java b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/mixins/CommandJdo_download.java
similarity index 61%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_download.java
rename to extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/mixins/CommandJdo_download.java
index 766e6ac..82848f5 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_download.java
+++ b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/mixins/CommandJdo_download.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl.mixins;
+package org.apache.isis.extensions.commandreplay.primary.mixins;
 
 import javax.inject.Inject;
 
@@ -9,29 +9,27 @@ import org.apache.isis.applib.annotation.ParameterLayout;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.value.Clob;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
-import org.apache.isis.extensions.commandreplay.impl.IsisModuleExtCommandReplayImpl;
+import org.apache.isis.extensions.commandreplay.primary.IsisModuleExtCommandReplayPrimary;
+import org.apache.isis.extensions.commandreplay.primary.ui.CommandReplayOnPrimaryService;
 
-import org.apache.isis.extensions.commandreplay.impl.ui.CommandReplayOnPrimaryService;
 
-@Action(
-        semantics = SemanticsOf.NON_IDEMPOTENT,
-        domainEvent = CommandJdo_download.ActionDomainEvent.class
+import lombok.RequiredArgsConstructor;
 
+@Action(
+    semantics = SemanticsOf.NON_IDEMPOTENT,
+    domainEvent = CommandJdo_download.ActionDomainEvent.class
 )
 @ActionLayout(
-        cssClassFa = "fa-download",
-        position = ActionLayout.Position.PANEL
+    cssClassFa = "fa-download",
+    position = ActionLayout.Position.PANEL
 )
+@RequiredArgsConstructor
 public class CommandJdo_download {
 
     public static class ActionDomainEvent
-            extends IsisModuleExtCommandReplayImpl.ActionDomainEvent<CommandJdo_download> { }
+            extends IsisModuleExtCommandReplayPrimary.ActionDomainEvent<CommandJdo_download> { }
 
     private final CommandJdo commandJdo;
-    public CommandJdo_download(CommandJdo commandJdo) {
-        this.commandJdo = commandJdo;
-    }
-
 
     @MemberOrder(name = "arguments", sequence = "1")
     public Clob act(
@@ -43,7 +41,8 @@ public class CommandJdo_download {
         return "command";
     }
 
-
     @Inject
     CommandReplayOnPrimaryService commandReplayOnPrimaryService;
+
+
 }
diff --git a/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/spiimpl/CaptureResultOfCommand.java b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/spiimpl/CaptureResultOfCommand.java
new file mode 100644
index 0000000..0328e29
--- /dev/null
+++ b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/spiimpl/CaptureResultOfCommand.java
@@ -0,0 +1,62 @@
+package org.apache.isis.extensions.commandreplay.primary.spiimpl;
+
+import javax.inject.Named;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.commanddto.conmap.UserDataKeys;
+import org.apache.isis.applib.services.commanddto.processor.spi.CommandDtoProcessorService;
+import org.apache.isis.applib.util.schema.CommandDtoUtils;
+import org.apache.isis.core.commons.internal.exceptions._Exceptions;
+import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
+import org.apache.isis.schema.cmd.v2.CommandDto;
+import org.apache.isis.schema.common.v2.PeriodDto;
+
+import lombok.val;
+
+/**
+ * Uses the SPI infrastructure to copy over standard properties from {@link Command} to {@link CommandDto}.
+ */
+@Service
+@Named("isisExtensionsCommandReplayPrimary.CaptureResultOfCommand")
+// specify quite a high priority since custom processors will probably want to run after this one
+// (but can choose to run before if they wish)
+@Order(OrderPrecedence.EARLY)
+public class CaptureResultOfCommand implements CommandDtoProcessorService {
+
+    @Override
+    public CommandDto process(final Object domainObject, CommandDto commandDto) {
+
+        if (!(domainObject instanceof CommandJdo)) {
+            return commandDto;
+        }
+
+        val commandJdo = (CommandJdo) domainObject;
+        if(commandDto == null) {
+            commandDto = commandJdo.getCommandDto();
+        }
+
+        final Bookmark result = commandJdo.getResult();
+        CommandDtoUtils.setUserData(commandDto,
+                UserDataKeys.RESULT, result != null ? result.toString() : null);
+
+        // knowing whether there was an exception is on the primary is
+        // used to determine whether to continue when replayed on the
+        // secondary if an exception occurs there also
+        CommandDtoUtils.setUserData(commandDto,
+                UserDataKeys.EXCEPTION,
+                commandJdo.getException());
+
+        val timings = CommandDtoUtils.timingsFor(commandDto);
+        timings.setStartedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(commandJdo.getStartedAt()));
+        timings.setCompletedAt(JavaSqlXMLGregorianCalendarMarshalling.toXMLGregorianCalendar(commandJdo.getCompletedAt()));
+
+        return commandDto;
+    }
+}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/ui/CommandReplayOnPrimaryService.java b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/ui/CommandReplayOnPrimaryService.java
similarity index 82%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/ui/CommandReplayOnPrimaryService.java
rename to extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/ui/CommandReplayOnPrimaryService.java
index f0570e0..1cc08f5 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/ui/CommandReplayOnPrimaryService.java
+++ b/extensions/core/command-replay/primary/src/main/java/org/apache/isis/extensions/commandreplay/primary/ui/CommandReplayOnPrimaryService.java
@@ -1,10 +1,13 @@
-package org.apache.isis.extensions.commandreplay.impl.ui;
+package org.apache.isis.extensions.commandreplay.primary.ui;
 
 import java.util.List;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
 import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.springframework.core.annotation.Order;
 
 import org.apache.isis.applib.ApplicationException;
 import org.apache.isis.applib.annotation.Action;
@@ -13,15 +16,18 @@ import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.DomainServiceLayout;
 import org.apache.isis.applib.annotation.MemberOrder;
 import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.annotation.Optionality;
+import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.applib.annotation.Parameter;
 import org.apache.isis.applib.annotation.ParameterLayout;
 import org.apache.isis.applib.annotation.SemanticsOf;
-import org.apache.isis.applib.services.conmap.command.ContentMappingServiceForCommandsDto;
+import org.apache.isis.applib.services.commanddto.conmap.ContentMappingServiceForCommandsDto;
 import org.apache.isis.applib.services.jaxb.JaxbService;
 import org.apache.isis.applib.services.message.MessageService;
 import org.apache.isis.applib.value.Clob;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdoRepository;
-import org.apache.isis.extensions.commandreplay.impl.IsisModuleExtCommandReplayImpl;
+import org.apache.isis.extensions.commandreplay.primary.IsisModuleExtCommandReplayPrimary;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 import org.apache.isis.schema.cmd.v2.CommandsDto;
 
@@ -29,18 +35,20 @@ import lombok.Getter;
 import lombok.extern.log4j.Log4j2;
 
 @DomainService(
-        nature = NatureOfService.VIEW,
-        objectType = "isisExtensionsCommandReplay.CommandReplayOnPrimaryService"
+    nature = NatureOfService.VIEW,
+    objectType = "isisExtensionsCommandReplayPrimary.CommandReplayOnPrimaryService"
 )
 @DomainServiceLayout(
-        named = "Activity",
-        menuBar = DomainServiceLayout.MenuBar.SECONDARY
+    named = "Activity",
+    menuBar = DomainServiceLayout.MenuBar.SECONDARY
 )
+@Named("isisExtensionsCommandReplayPrimary.CommandReplayOnPrimaryService")
+@Order(OrderPrecedence.MIDPOINT)
 @Log4j2
 public class CommandReplayOnPrimaryService {
 
     public static abstract class ActionDomainEvent
-            extends IsisModuleExtCommandReplayImpl.ActionDomainEvent<CommandReplayOnPrimaryService> { }
+            extends IsisModuleExtCommandReplayPrimary.ActionDomainEvent<CommandReplayOnPrimaryService> { }
 
 
     public static class FindCommandsOnPrimarySinceDomainEvent extends ActionDomainEvent { }
@@ -58,7 +66,7 @@ public class CommandReplayOnPrimaryService {
      * These actions should be called with HTTP Accept Header set to:
      * <code>application/xml;profile="urn:org.restfulobjects:repr-types/action-result";x-ro-domain-type="org.apache.isis.schema.cmd.v1.CommandsDto"</code>
      *
-     * @param transactionId - to search from.  This transactionId will <i>not</i> be included in the response.
+     * @param uniqueId - to search from.  This transactionId will <i>not</i> be included in the response.
      * @param batchSize - the maximum number of commands to return.  If not specified, all found will be returned.
      *
      * @return
@@ -68,16 +76,16 @@ public class CommandReplayOnPrimaryService {
     @ActionLayout(cssClassFa = "fa-files-o")
     @MemberOrder(sequence="40")
     public List<CommandJdo> findCommandsOnPrimarySince(
-            @Nullable
-            @ParameterLayout(named="Transaction Id")
-            final UUID transactionId,
-            @Nullable
+            @Parameter(optionality = Optionality.OPTIONAL) // @Nullable
+            @ParameterLayout(named="Unique Id")
+            final UUID uniqueId,
+            @Parameter(optionality = Optionality.OPTIONAL) // @Nullable
             @ParameterLayout(named="Batch size")
             final Integer batchSize)
             throws NotFoundException {
-        final List<CommandJdo> commands = commandServiceRepository.findSince(transactionId, batchSize);
+        final List<CommandJdo> commands = commandServiceRepository.findSince(uniqueId, batchSize);
         if(commands == null) {
-            throw new NotFoundException(transactionId);
+            throw new NotFoundException(uniqueId);
         }
         return commands;
     }
@@ -172,6 +180,7 @@ public class CommandReplayOnPrimaryService {
         return uuid != null ? uuid.toString() : "00000000-0000-0000-0000-000000000000";
     }
 
+
     @Inject CommandJdoRepository commandServiceRepository;
     @Inject JaxbService jaxbService;
     @Inject MessageService messageService;
diff --git a/extensions/core/command-replay/impl/pom.xml b/extensions/core/command-replay/secondary/pom.xml
similarity index 92%
rename from extensions/core/command-replay/impl/pom.xml
rename to extensions/core/command-replay/secondary/pom.xml
index 195e92b..9e0e49e 100644
--- a/extensions/core/command-replay/impl/pom.xml
+++ b/extensions/core/command-replay/secondary/pom.xml
@@ -19,8 +19,8 @@
         <version>2.0.0-SNAPSHOT</version>
     </parent>
 
-    <artifactId>isis-extensions-command-replay-impl</artifactId>
-    <name>Apache Isis Ext - Command Replay Implementation</name>
+    <artifactId>isis-extensions-command-replay-secondary</artifactId>
+    <name>Apache Isis Ext - Command Replay for Secondary</name>
 
     <packaging>jar</packaging>
 
@@ -30,8 +30,8 @@
     </description>
 
     <properties>
-        <jar-plugin.automaticModuleName>org.apache.isis.extensions.commandreplay.impl</jar-plugin.automaticModuleName>
-        <git-plugin.propertiesDir>org/apache/isis/extensions/commandreplay</git-plugin.propertiesDir>
+        <jar-plugin.automaticModuleName>org.apache.isis.extensions.commandreplay.secondary</jar-plugin.automaticModuleName>
+        <git-plugin.propertiesDir>org/apache/isis/extensions/commandreplay/secondary</git-plugin.propertiesDir>
     </properties>
 
     <dependencies>
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/IsisModuleExtCommandReplayImpl.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/IsisModuleExtCommandReplaySecondary.java
similarity index 77%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/IsisModuleExtCommandReplayImpl.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/IsisModuleExtCommandReplaySecondary.java
index a7d27ff..c7e115c 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/IsisModuleExtCommandReplayImpl.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/IsisModuleExtCommandReplaySecondary.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl;
+package org.apache.isis.extensions.commandreplay.secondary;
 
 
 import javax.inject.Inject;
@@ -21,17 +21,15 @@ import org.springframework.scheduling.quartz.SpringBeanJobFactory;
 
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.extensions.commandlog.impl.IsisModuleExtCommandLogImpl;
-import org.apache.isis.extensions.commandreplay.impl.analysis.CommandReplayAnalyserException;
-import org.apache.isis.extensions.commandreplay.impl.analysis.CommandReplayAnalyserResult;
-import org.apache.isis.extensions.commandreplay.impl.executor.CommandExecutorServiceWithTime;
-import org.apache.isis.extensions.commandreplay.impl.fetch.CommandFetcher;
-import org.apache.isis.extensions.commandreplay.impl.fetch.PrimaryConfig;
-import org.apache.isis.extensions.commandreplay.impl.clock.TickingClockService;
-import org.apache.isis.extensions.commandreplay.impl.analysis.CommandReplayAnalysisService;
-import org.apache.isis.extensions.commandreplay.impl.fetch.SecondaryConfig;
-import org.apache.isis.extensions.commandreplay.impl.job.ReplicateAndReplayJob;
-import org.apache.isis.extensions.commandreplay.impl.ui.CommandReplayOnPrimaryService;
-import org.apache.isis.extensions.commandreplay.impl.ui.CommandReplayOnSecondaryService;
+import org.apache.isis.extensions.commandreplay.secondary.analyser.CommandReplayAnalyserException;
+import org.apache.isis.extensions.commandreplay.secondary.analyser.CommandReplayAnalyserResult;
+import org.apache.isis.extensions.commandreplay.secondary.analysis.CommandReplayAnalysisService;
+import org.apache.isis.extensions.commandreplay.secondary.clock.TickingClockService;
+import org.apache.isis.extensions.commandreplay.secondary.executor.CommandExecutorServiceWithTime;
+import org.apache.isis.extensions.commandreplay.secondary.fetch.CommandFetcher;
+import org.apache.isis.extensions.commandreplay.secondary.config.SecondaryConfig;
+import org.apache.isis.extensions.commandreplay.secondary.job.ReplicateAndReplayJob;
+import org.apache.isis.extensions.commandreplay.secondary.ui.CommandReplayOnSecondaryService;
 import org.apache.isis.extensions.quartz.IsisModuleExtQuartzImpl;
 import org.apache.isis.extensions.quartz.spring.AutowiringSpringBeanJobFactory;
 
@@ -43,26 +41,23 @@ import lombok.val;
         IsisModuleExtCommandLogImpl.class,
         IsisModuleExtQuartzImpl.class,
 
-        // @DomainService's
+        // @Service's
         CommandExecutorServiceWithTime.class,
         CommandFetcher.class,
         CommandReplayAnalyserResult.class,
         CommandReplayAnalyserException.class,
         CommandReplayAnalysisService.class,
-        CommandReplayOnPrimaryService.class,
         CommandReplayOnSecondaryService.class,
         TickingClockService.class,
 
         // @Service's
-        PrimaryConfig.class,
         SecondaryConfig.class,
-
 })
 @Profile("secondary")
-public class IsisModuleExtCommandReplayImpl {
+public class IsisModuleExtCommandReplaySecondary {
 
     public abstract static class ActionDomainEvent<S>
-        extends org.apache.isis.applib.events.domain.ActionDomainEvent<S> { }
+            extends org.apache.isis.applib.events.domain.ActionDomainEvent<S> { }
 
     public abstract static class CollectionDomainEvent<S,T>
             extends org.apache.isis.applib.events.domain.CollectionDomainEvent<S,T> { }
@@ -70,7 +65,6 @@ public class IsisModuleExtCommandReplayImpl {
     public abstract static class PropertyDomainEvent<S,T>
             extends org.apache.isis.applib.events.domain.PropertyDomainEvent<S,T> { }
 
-
     @Inject ApplicationContext applicationContext;
     @Inject IsisConfiguration isisConfiguration;
 
@@ -87,7 +81,7 @@ public class IsisModuleExtCommandReplayImpl {
     public SimpleTriggerFactoryBean replicateAndReplayTriggerFactory(@Qualifier("ReplicateAndReplayJob") JobDetail job) {
         val triggerFactory = new SimpleTriggerFactoryBean();
         triggerFactory.setJobDetail(job);
-        val config = isisConfiguration.getExtensions().getCommandReplay().getSecondary().getQuartzReplicateAndReplayJob();
+        val config = isisConfiguration.getExtensions().getCommandReplay().getQuartzReplicateAndReplayJob();
         triggerFactory.setRepeatInterval(config.getRepeatInterval());
         triggerFactory.setStartDelay(config.getStartDelay());
         triggerFactory.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/SecondaryStatus.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/SecondaryStatus.java
similarity index 76%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/SecondaryStatus.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/SecondaryStatus.java
index 1cc6c96..00eeb13 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/SecondaryStatus.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/SecondaryStatus.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl;
+package org.apache.isis.extensions.commandreplay.secondary;
 
 public enum SecondaryStatus {
     TICKING_CLOCK_STATUS_UNKNOWN,
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/StatusException.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/StatusException.java
similarity index 85%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/StatusException.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/StatusException.java
index 6ba46e6..1746e68 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/StatusException.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/StatusException.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl;
+package org.apache.isis.extensions.commandreplay.secondary;
 
 
 public class StatusException extends Exception {
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyser.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyser.java
similarity index 82%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyser.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyser.java
index e4d3e50..3e06c2a 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyser.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyser.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl.analysis;
+package org.apache.isis.extensions.commandreplay.secondary.analyser;
 
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
 
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyserException.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyserException.java
similarity index 79%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyserException.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyserException.java
index 5039a11..95147dd 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyserException.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyserException.java
@@ -1,20 +1,25 @@
-package org.apache.isis.extensions.commandreplay.impl.analysis;
+package org.apache.isis.extensions.commandreplay.secondary.analyser;
 
 import javax.annotation.PostConstruct;
+import javax.inject.Named;
 
 import com.google.common.base.Objects;
 
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.services.conmap.command.UserDataKeys;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.applib.services.commanddto.conmap.UserDataKeys;
 import org.apache.isis.applib.util.schema.CommandDtoUtils;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
-import org.apache.isis.schema.cmd.v2.CommandDto;
 
 import lombok.RequiredArgsConstructor;
 import lombok.val;
 
-@DomainService()
+@Service
+@Named("isisExtensionsCommandReplaySecondary.CommandReplayAnalyserException")
+@Order(OrderPrecedence.MIDPOINT)
 @RequiredArgsConstructor
 public class CommandReplayAnalyserException implements CommandReplayAnalyser {
 
@@ -23,7 +28,7 @@ public class CommandReplayAnalyserException implements CommandReplayAnalyser {
 
     @PostConstruct
     public void init() {
-        enabled = isisConfiguration.getExtensions().getCommandReplay().getSecondary().getAnalyser().getResult().isEnabled();
+        enabled = isisConfiguration.getExtensions().getCommandReplay().getAnalyser().getResult().isEnabled();
     }
 
     @Override
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyserResult.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyserResult.java
similarity index 75%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyserResult.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyserResult.java
index 3db2db2..dc1012d 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalyserResult.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analyser/CommandReplayAnalyserResult.java
@@ -1,12 +1,15 @@
-package org.apache.isis.extensions.commandreplay.impl.analysis;
+package org.apache.isis.extensions.commandreplay.secondary.analyser;
 
 import javax.annotation.PostConstruct;
+import javax.inject.Named;
 
 import com.google.common.base.Objects;
 
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.conmap.command.UserDataKeys;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.applib.services.commanddto.conmap.UserDataKeys;
 import org.apache.isis.applib.util.schema.CommandDtoUtils;
 
 import org.apache.isis.core.config.IsisConfiguration;
@@ -16,7 +19,9 @@ import org.apache.isis.schema.cmd.v2.CommandDto;
 import lombok.RequiredArgsConstructor;
 import lombok.val;
 
-@DomainService()
+@Service
+@Named("isisExtensionsCommandReplaySecondary.CommandReplayAnalyserResult")
+@Order(OrderPrecedence.MIDPOINT)
 @RequiredArgsConstructor
 public class CommandReplayAnalyserResult implements CommandReplayAnalyser {
 
@@ -25,7 +30,7 @@ public class CommandReplayAnalyserResult implements CommandReplayAnalyser {
 
     @PostConstruct
     public void init() {
-        enabled = isisConfiguration.getExtensions().getCommandReplay().getSecondary().getAnalyser().getException().isEnabled();
+        enabled = isisConfiguration.getExtensions().getCommandReplay().getAnalyser().getException().isEnabled();
     }
 
     @Override
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalysisService.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analysis/CommandReplayAnalysisService.java
similarity index 74%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalysisService.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analysis/CommandReplayAnalysisService.java
index dc2e223..fa2d864 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/analysis/CommandReplayAnalysisService.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/analysis/CommandReplayAnalysisService.java
@@ -1,15 +1,23 @@
-package org.apache.isis.extensions.commandreplay.impl.analysis;
+package org.apache.isis.extensions.commandreplay.secondary.analysis;
 
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
+import org.apache.isis.extensions.commandreplay.secondary.analyser.CommandReplayAnalyser;
 
 import lombok.extern.log4j.Log4j2;
 
-@DomainService()
+@Service
+@Named("isisExtensionsCommandReplaySecondary.CommandReplayAnalysisService")
+@Order(OrderPrecedence.MIDPOINT)
 @Log4j2
 public class CommandReplayAnalysisService {
 
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/clock/TickingClockService.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/clock/TickingClockService.java
similarity index 78%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/clock/TickingClockService.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/clock/TickingClockService.java
index 0f7a798..bee85da 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/clock/TickingClockService.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/clock/TickingClockService.java
@@ -1,20 +1,20 @@
-package org.apache.isis.extensions.commandreplay.impl.clock;
+package org.apache.isis.extensions.commandreplay.secondary.clock;
 
 import java.sql.Timestamp;
-import java.util.Optional;
 import java.util.function.Supplier;
 
 import javax.annotation.PostConstruct;
-import javax.inject.Inject;
+import javax.inject.Named;
 
-import org.springframework.context.annotation.Profile;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
 
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.applib.clock.Clock;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.testing.fixtures.applib.clock.TickingFixtureClock;
 
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
@@ -35,22 +35,25 @@ import lombok.extern.log4j.Log4j2;
  *     systems, eg a replay secondary.
  * </p>
  */
-@DomainService()
+@Service()
+@Named("isisExtensionsCommandReplaySecondary.TickingClockService")
+@Order(OrderPrecedence.MIDPOINT)
 @Log4j2
+@RequiredArgsConstructor
 public class TickingClockService {
 
-    @Inject IsisConfiguration isisConfiguration;
+    final IsisConfiguration isisConfiguration;
 
     @PostConstruct
     public void init() {
-        val baseUrl = isisConfiguration.getExtensions().getCommandReplay().getPrimary().getBaseUrl();
-        val user = isisConfiguration.getExtensions().getCommandReplay().getPrimary().getUser();
-        val password = isisConfiguration.getExtensions().getCommandReplay().getPrimary().getPassword();
+        val baseUrl = isisConfiguration.getExtensions().getCommandReplay().getPrimaryAccess().getBaseUrlRestful();
+        val user = isisConfiguration.getExtensions().getCommandReplay().getPrimaryAccess().getUser();
+        val password = isisConfiguration.getExtensions().getCommandReplay().getPrimaryAccess().getPassword();
 
         if( !baseUrl.isPresent()||
             !user.isPresent() ||
             !password.isPresent()) {
-            log.info("init() - skipping, one or more 'isis.extensions.command-replay.primary' configuration constants missing");
+            log.warn("init() - skipping, one or more 'isis.extensions.command-replay.primary' configuration properties has not been set");
             return;
         }
 
@@ -120,7 +123,11 @@ public class TickingClockService {
     private void ensureInitialized() {
         if(!isInitialized()) {
             throw new IllegalStateException(
-                    "Not initialized.  Make sure that the application is configured to run as a replay secondary");
+                "Not initialized.  " +
+                "Make sure that the application is configured as a " +
+                "replay secondary by configuring the " +
+                "'isis.extensions.command-replay.primary' " +
+                "configuration properties.");
         }
     }
 
diff --git a/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/config/SecondaryConfig.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/config/SecondaryConfig.java
new file mode 100644
index 0000000..00f3a4f
--- /dev/null
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/config/SecondaryConfig.java
@@ -0,0 +1,54 @@
+package org.apache.isis.extensions.commandreplay.secondary.config;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.validation.constraints.NotNull;
+
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.core.config.IsisConfiguration;
+
+import lombok.Getter;
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
+
+@Service
+@Named("isisExtensionsCommandReplaySecondary.SecondaryConfig")
+@Order(OrderPrecedence.MIDPOINT)
+@Log4j2
+public class SecondaryConfig {
+
+    @Getter final String primaryUser;
+    @Getter final String primaryPassword;
+    @Getter final String primaryBaseUrlRestful;
+    @Getter final String primaryBaseUrlWicket;
+    @Getter final int batchSize;
+
+    @Getter final String quartzUser;
+    @Getter final List<String> quartzRoles;
+
+    public SecondaryConfig(@NotNull final IsisConfiguration isisConfiguration) {
+        val config = isisConfiguration.getExtensions().getCommandReplay();
+
+        val primaryAccess = config.getPrimaryAccess();
+        primaryUser = primaryAccess.getUser().orElse(null);
+        primaryPassword = primaryAccess.getPassword().orElse(null);
+        primaryBaseUrlRestful = primaryAccess.getBaseUrlRestful().orElse(null);
+        primaryBaseUrlWicket = primaryAccess.getBaseUrlWicket().orElse(null);
+        batchSize = config.getBatchSize();
+
+        quartzUser = config.getQuartzSession().getUser();
+        quartzRoles = config.getQuartzSession().getRoles();
+    }
+
+    public boolean isConfigured() {
+        return primaryUser != null &&
+               primaryPassword != null &&
+               primaryBaseUrlRestful != null &&
+               quartzUser != null &&
+               quartzRoles != null;
+    }
+}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/executor/CommandExecutorServiceWithTime.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/executor/CommandExecutorServiceWithTime.java
similarity index 84%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/executor/CommandExecutorServiceWithTime.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/executor/CommandExecutorServiceWithTime.java
index 6169627..392f886 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/executor/CommandExecutorServiceWithTime.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/executor/CommandExecutorServiceWithTime.java
@@ -14,25 +14,24 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
-package org.apache.isis.extensions.commandreplay.impl.executor;
+package org.apache.isis.extensions.commandreplay.secondary.executor;
 
-import java.sql.Timestamp;
-import java.util.concurrent.Callable;
 import java.util.function.Supplier;
 
 import javax.inject.Named;
 
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
 
-import org.apache.isis.applib.annotation.DomainService;
+import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.applib.jaxb.JavaSqlXMLGregorianCalendarMarshalling;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.CommandExecutorService;
-import org.apache.isis.extensions.commandreplay.impl.clock.TickingClockService;
+import org.apache.isis.extensions.commandreplay.secondary.clock.TickingClockService;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
-import lombok.SneakyThrows;
 import lombok.extern.log4j.Log4j2;
 
 /**
@@ -43,7 +42,10 @@ import lombok.extern.log4j.Log4j2;
  *     It then delegates down to the default implementation.
  * </p>
  */
-@DomainService()
+@Service
+@Named("isisExtensionsCommandReplaySecondary.CommandExecutorServiceWithTime")
+@Order(OrderPrecedence.MIDPOINT - 10) // before CommandExecutorServiceDefault
+@Qualifier("WithTime")
 @Log4j2
 public class CommandExecutorServiceWithTime implements CommandExecutorService {
 
@@ -51,10 +53,8 @@ public class CommandExecutorServiceWithTime implements CommandExecutorService {
     final TickingClockService tickingClockService;
 
     public CommandExecutorServiceWithTime(
-            //@Named("isisRuntimeServices.CommandExecutorServiceDefault")
-            @Qualifier("Default")
-            CommandExecutorService delegate
-            , TickingClockService tickingClockService) {
+            @Qualifier("Default") final CommandExecutorService delegate,
+            final TickingClockService tickingClockService) {
         this.delegate = delegate;
         this.tickingClockService = tickingClockService;
     }
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/CommandFetcher.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/fetch/CommandFetcher.java
similarity index 72%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/CommandFetcher.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/fetch/CommandFetcher.java
index 27ec9a7..e4b9d47 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/fetch/CommandFetcher.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/fetch/CommandFetcher.java
@@ -1,18 +1,24 @@
-package org.apache.isis.extensions.commandreplay.impl.fetch;
+package org.apache.isis.extensions.commandreplay.secondary.fetch;
 
 import java.net.URI;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 
-import org.apache.isis.applib.annotation.DomainService;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
+
+import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.applib.services.jaxb.JaxbService;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
-import org.apache.isis.extensions.commandreplay.impl.SecondaryStatus;
-import org.apache.isis.extensions.commandreplay.impl.StatusException;
+import org.apache.isis.extensions.commandreplay.secondary.SecondaryStatus;
+import org.apache.isis.extensions.commandreplay.secondary.StatusException;
+import org.apache.isis.extensions.commandreplay.secondary.config.SecondaryConfig;
 import org.apache.isis.extensions.jaxrsclient.applib.client.JaxRsClient;
 import org.apache.isis.extensions.jaxrsclient.applib.client.JaxRsResponse;
 import org.apache.isis.extensions.jaxrsclient.impl.client.JaxRsClientDefault;
@@ -22,28 +28,30 @@ import org.apache.isis.schema.cmd.v2.CommandsDto;
 import lombok.extern.log4j.Log4j2;
 
 
-@DomainService()
+@Service()
+@Named("isisExtensionsCommandReplaySecondary.CommandFetcher")
+@Order(OrderPrecedence.MIDPOINT)
 @Log4j2
 public class CommandFetcher {
 
     static final String URL_SUFFIX =
-            "services/isisExtensionsCommandReplay.CommandReplayOnPrimaryService/actions/findCommandsOnPrimarySince/invoke";
+            "services/isisExtensionsCommandReplayPrimary.CommandReplayOnPrimaryService/actions/findCommandsOnPrimarySince/invoke";
 
 
     /**
      * Replicates a single command.
      *
-     * @param previousHwm
+     * @param previousHwmIfAny
      * @return
      * @throws StatusException
      */
     public CommandDto fetchCommand(
-            final CommandJdo previousHwm)
+            @Nullable final CommandJdo previousHwmIfAny)
             throws StatusException {
 
         log.debug("finding command on primary ...");
 
-        final CommandsDto commandsDto = fetchCommands(previousHwm);
+        final CommandsDto commandsDto = fetchCommands(previousHwmIfAny);
 
         if (commandsDto == null) {
             return null;
@@ -58,10 +66,12 @@ public class CommandFetcher {
     /**
      * @return - the commands, or <tt>null</tt> if none were found
      * @throws StatusException
-     * @param previousHwm
+     * @param previousHwmIfAny
      */
-    private CommandsDto fetchCommands(final CommandJdo previousHwm) throws StatusException {
-        final UUID transactionId = previousHwm != null ? previousHwm.getUniqueId() : null;
+    private CommandsDto fetchCommands(final CommandJdo previousHwmIfAny)
+            throws StatusException {
+
+        final UUID transactionId = previousHwmIfAny != null ? previousHwmIfAny.getUniqueId() : null;
 
         log.debug("finding commands on primary ...");
 
@@ -79,15 +89,15 @@ public class CommandFetcher {
     }
 
 
-    private URI buildUri(final UUID transactionId) {
+    private URI buildUri(final UUID uniqueId) {
         final UriBuilder uriBuilder = UriBuilder.fromUri(
-                transactionId != null
+                uniqueId != null
                         ? String.format(
-                        "%s%s?transactionId=%s&batchSize=%d",
-                        primaryConfig.getBaseUrl(), URL_SUFFIX, transactionId, primaryConfig.getBatchSize())
+                        "%s%s?uniqueId=%s&batchSize=%d",
+                        secondaryConfig.getPrimaryBaseUrlRestful(), URL_SUFFIX, uniqueId, secondaryConfig.getBatchSize())
                         : String.format(
                         "%s%s?batchSize=%d",
-                        primaryConfig.getBaseUrl(), URL_SUFFIX, primaryConfig.getBatchSize())
+                        secondaryConfig.getPrimaryBaseUrlWicket(), URL_SUFFIX, secondaryConfig.getBatchSize())
         );
         final URI uri = uriBuilder.build();
         log.info("uri = {}", uri);
@@ -98,8 +108,8 @@ public class CommandFetcher {
         final JaxRsResponse response;
         final JaxRsClient jaxRsClient = new JaxRsClientDefault();
         try {
-            final String user = primaryConfig.getUser();
-            final String password = primaryConfig.getPassword();
+            final String user = secondaryConfig.getPrimaryUser();
+            final String password = secondaryConfig.getPrimaryPassword();
             response = jaxRsClient.get(uri, CommandsDto.class, JaxRsClient.ReprType.ACTION_RESULT, user, password);
             int status = response.getStatus();
             if(status != Response.Status.OK.getStatusCode()) {
@@ -142,6 +152,6 @@ public class CommandFetcher {
     }
 
     @Inject
-    PrimaryConfig primaryConfig;
+    SecondaryConfig secondaryConfig;
 
 }
\ No newline at end of file
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/ReplicateAndReplayJob.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/job/ReplicateAndReplayJob.java
similarity index 80%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/ReplicateAndReplayJob.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/job/ReplicateAndReplayJob.java
index cde212b..4c6000a 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/ReplicateAndReplayJob.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/job/ReplicateAndReplayJob.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl.job;
+package org.apache.isis.extensions.commandreplay.secondary.job;
 
 import javax.inject.Inject;
 
@@ -12,11 +12,10 @@ import org.apache.isis.core.runtime.iactn.IsisInteractionFactory;
 import org.apache.isis.core.security.authentication.AuthenticationSession;
 import org.apache.isis.core.security.authentication.standard.SimpleSession;
 
-import org.apache.isis.extensions.commandreplay.impl.fetch.SecondaryConfig;
-import org.apache.isis.extensions.commandreplay.impl.job.callables.IsTickingClockInitialized;
-import org.apache.isis.extensions.commandreplay.impl.job.callables.ReplicateAndRunCommands;
-import org.apache.isis.extensions.commandreplay.impl.fetch.PrimaryConfig;
-import org.apache.isis.extensions.commandreplay.impl.SecondaryStatus;
+import org.apache.isis.extensions.commandreplay.secondary.config.SecondaryConfig;
+import org.apache.isis.extensions.commandreplay.secondary.jobcallables.IsTickingClockInitialized;
+import org.apache.isis.extensions.commandreplay.secondary.jobcallables.ReplicateAndRunCommands;
+import org.apache.isis.extensions.commandreplay.secondary.SecondaryStatus;
 
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -26,7 +25,6 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 public class ReplicateAndReplayJob implements Job {
 
-    @Inject PrimaryConfig primaryConfig;
     @Inject SecondaryConfig secondaryConfig;
 
     AuthenticationSession authSession;
@@ -36,8 +34,8 @@ public class ReplicateAndReplayJob implements Job {
         // figure out if this instance is configured to run as primary or secondary
         new SecondaryStatusData(quartzContext);
 
-        if(primaryConfig.isConfigured() && secondaryConfig.isConfigured()) {
-            authSession = new SimpleSession(secondaryConfig.getUser(), secondaryConfig.getRoles());
+        if(secondaryConfig.isConfigured()) {
+            authSession = new SimpleSession(secondaryConfig.getPrimaryUser(), secondaryConfig.getQuartzRoles());
             exec(quartzContext);
         }
     }
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/SecondaryStatusData.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/job/SecondaryStatusData.java
similarity index 87%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/SecondaryStatusData.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/job/SecondaryStatusData.java
index d9f3d73..1866787 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/SecondaryStatusData.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/job/SecondaryStatusData.java
@@ -1,8 +1,8 @@
-package org.apache.isis.extensions.commandreplay.impl.job;
+package org.apache.isis.extensions.commandreplay.secondary.job;
 
 import org.quartz.JobExecutionContext;
 
-import org.apache.isis.extensions.commandreplay.impl.SecondaryStatus;
+import org.apache.isis.extensions.commandreplay.secondary.SecondaryStatus;
 import org.apache.isis.extensions.quartz.context.JobExecutionData;
 
 import lombok.val;
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/callables/IsTickingClockInitialized.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/jobcallables/IsTickingClockInitialized.java
similarity index 75%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/callables/IsTickingClockInitialized.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/jobcallables/IsTickingClockInitialized.java
index 9a8b6c9..c13f70f 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/callables/IsTickingClockInitialized.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/jobcallables/IsTickingClockInitialized.java
@@ -1,11 +1,11 @@
-package org.apache.isis.extensions.commandreplay.impl.job.callables;
+package org.apache.isis.extensions.commandreplay.secondary.jobcallables;
 
 import java.util.concurrent.Callable;
 
 import javax.inject.Inject;
 
 import org.apache.isis.applib.services.xactn.TransactionService;
-import org.apache.isis.extensions.commandreplay.impl.clock.TickingClockService;
+import org.apache.isis.extensions.commandreplay.secondary.clock.TickingClockService;
 
 public class IsTickingClockInitialized implements Callable<Boolean> {
 
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/callables/ReplicateAndRunCommands.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/jobcallables/ReplicateAndRunCommands.java
similarity index 58%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/callables/ReplicateAndRunCommands.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/jobcallables/ReplicateAndRunCommands.java
index 2b805c7..f345ed5 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/job/callables/ReplicateAndRunCommands.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/jobcallables/ReplicateAndRunCommands.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl.job.callables;
+package org.apache.isis.extensions.commandreplay.secondary.jobcallables;
 
 import java.util.Optional;
 import java.util.concurrent.Callable;
@@ -10,15 +10,25 @@ import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdoRepository;
 import org.apache.isis.extensions.commandlog.impl.jdo.ReplayState;
-import org.apache.isis.extensions.commandreplay.impl.SecondaryStatus;
-import org.apache.isis.extensions.commandreplay.impl.StatusException;
-import org.apache.isis.extensions.commandreplay.impl.analysis.CommandReplayAnalysisService;
-import org.apache.isis.extensions.commandreplay.impl.fetch.CommandFetcher;
-import org.apache.isis.extensions.commandreplay.impl.spi.ReplayCommandExecutionController;
+import org.apache.isis.extensions.commandreplay.secondary.SecondaryStatus;
+import org.apache.isis.extensions.commandreplay.secondary.StatusException;
+import org.apache.isis.extensions.commandreplay.secondary.analysis.CommandReplayAnalysisService;
+import org.apache.isis.extensions.commandreplay.secondary.fetch.CommandFetcher;
+import org.apache.isis.extensions.commandreplay.secondary.spi.ReplayCommandExecutionController;
 
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
+/**
+ * Encodes the algorithm for fetching commands from the primary, and
+ * replaying on the secondary.
+ *
+ * <p>
+ *     This class is instantiated each time the Quartz job
+ *     (<code>{@link org.apache.isis.extensions.commandreplay.secondary.job.ReplicateAndReplayJob}</code>)
+ *     files.
+ * </p>
+ */
 @Log4j2
 public class ReplicateAndRunCommands implements Callable<SecondaryStatus> {
 
@@ -29,7 +39,6 @@ public class ReplicateAndRunCommands implements Callable<SecondaryStatus> {
     @Inject CommandReplayAnalysisService analysisService;
     @Inject Optional<ReplayCommandExecutionController> controller;
 
-
     @Override
     public SecondaryStatus call() throws Exception {
         try {
@@ -42,77 +51,53 @@ public class ReplicateAndRunCommands implements Callable<SecondaryStatus> {
 
     private void doCall() throws  StatusException  {
 
-        CommandJdo hwmCommand = null;
         if(!isRunning()) {
             log.debug("ReplicateAndRunCommands is paused");
             return;
         }
 
+        CommandJdo replicatedHwm = null;
         while(isRunning()) {
 
-            if(hwmCommand == null) {
+            if(replicatedHwm == null) {
                 // first time through the loop we need to find the HWM command
                 // on the secondary.  On subsequent iterations we use the
                 // command from before as the HWM
-                log.debug("searching for hwm on secondary ...");
-                hwmCommand = commandJdoRepository.findReplayedHwm();
+                replicatedHwm = commandJdoRepository.findReplicatedHwm();
             }
 
-            if(hwmCommand == null) {
-                log.debug("could not find HWM on secondary, breaking");
+            if(replicatedHwm != null && replicatedHwm.getReplayState().isFailed()) {
+                log.info("Command {} hit replay error", replicatedHwm.getUniqueId());
                 return;
             }
 
-            log.debug("current hwm {} @ {} : {}",
-                    hwmCommand.getUniqueId(), hwmCommand.getTimestamp(),
-                    hwmCommand.getLogicalMemberIdentifier());
-
-
-            boolean fetchNext;
-            if(hwmCommand.getReplayState() == null || hwmCommand.getReplayState() == ReplayState.PENDING) {
 
-                // the HWM has not been replayed.
-                // this might be because it has been marked for retry by the administrator.
-                // so, we will just use it directly
+            if(isFetchRequired(replicatedHwm)) {
 
-                fetchNext = false;
-            } else {
-                //
-                // check that the current HWM was replayed successfully, otherwise break out
-                //
-                if(hwmCommand.getReplayState().isFailed()) {
-                    log.info("Command xactnId={} hit replay error", hwmCommand.getUniqueId());
-                    return;
-                }
-                fetchNext = true;
-            }
-
-            if(fetchNext) {
-                //
                 // replicate next command from primary (if any)
-                //
-                val commandDto = commandFetcher.fetchCommand(hwmCommand);
+                val commandDto = commandFetcher.fetchCommand(replicatedHwm);
                 if (commandDto == null) {
                     log.info("No more commands found, breaking out");
                     return;
                 }
 
-                hwmCommand = transactionService.executeWithinTransaction(
+                replicatedHwm = transactionService.executeWithinTransaction(
                         () -> commandJdoRepository.saveForReplay(commandDto));
             }
 
-            log.info("next HWM transactionId = {}", hwmCommand.getUniqueId());
+            assert replicatedHwm != null;
+            log.info("next HWM {}", replicatedHwm.getUniqueId());
 
 
             //
             // run command
             //
-            executeCommandInTran(hwmCommand);
+            executeCommandInTran(replicatedHwm);
 
             //
             // find child commands, and run them
             //
-            val parent = hwmCommand;
+            val parent = replicatedHwm;
             val childCommands =
                     transactionService.executeWithinTransaction(
                         () -> commandJdoRepository.findByParent(parent));
@@ -130,6 +115,30 @@ public class ReplicateAndRunCommands implements Callable<SecondaryStatus> {
         }
     }
 
+    private boolean isFetchRequired(final CommandJdo replicatedHwm) {
+
+        boolean fetchNext;
+        if(replicatedHwm == null) {
+            log.debug("could not find any replicated HWM");
+            return true;
+
+        }
+
+        val notYetReplayed = replicatedHwm.getReplayState() == null || replicatedHwm.getReplayState() == ReplayState.PENDING;
+        // the replicated HWM command might not yet been replayed.
+        // For example, this could be because it has been marked for retry
+        // by the administrator.  if so, no need to fetch another command from the primary.
+
+        log.debug("current replicated hwm {} @ {} : {} has " +
+                        (notYetReplayed? "NOT yet ": "") +
+                        "been replayed",
+                replicatedHwm.getUniqueId(), replicatedHwm.getTimestamp(),
+                replicatedHwm.getLogicalMemberIdentifier());
+
+        return !notYetReplayed;
+
+    }
+
     private void executeCommandInTran(final CommandJdo command) {
         val commandDto = command.getCommandDto();
         transactionService.executeWithinTransaction(
@@ -148,5 +157,4 @@ public class ReplicateAndRunCommands implements Callable<SecondaryStatus> {
 
     }
 
-
 }
\ No newline at end of file
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_exclude.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_exclude.java
similarity index 66%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_exclude.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_exclude.java
index 05ef148..64676d2 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_exclude.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_exclude.java
@@ -1,4 +1,8 @@
-package org.apache.isis.extensions.commandreplay.impl.mixins;
+package org.apache.isis.extensions.commandreplay.secondary.mixins;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
 
 import org.apache.isis.applib.annotation.Action;
 import org.apache.isis.applib.annotation.MemberOrder;
@@ -6,25 +10,24 @@ import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.extensions.commandlog.impl.IsisModuleExtCommandLogImpl;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
 import org.apache.isis.extensions.commandlog.impl.jdo.ReplayState;
+import org.apache.isis.extensions.commandreplay.secondary.config.SecondaryConfig;
 
+import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
 
 @Action(
-        semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
-        domainEvent = CommandJdo_exclude.ActionDomainEvent.class
-
+    semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
+    domainEvent = CommandJdo_exclude.ActionDomainEvent.class
 )
+@RequiredArgsConstructor
 @Log4j2
 public class CommandJdo_exclude {
 
     public static class ActionDomainEvent
             extends IsisModuleExtCommandLogImpl.ActionDomainEvent<CommandJdo_exclude> { }
 
-    private final CommandJdo commandJdo;
-    public CommandJdo_exclude(CommandJdo commandJdo) {
-        this.commandJdo = commandJdo;
-    }
+    final CommandJdo commandJdo;
 
     @MemberOrder(name = "executeIn", sequence = "2")
     public CommandJdo act() {
@@ -33,7 +36,7 @@ public class CommandJdo_exclude {
     }
 
     public boolean hideAct() {
-        return commandJdo.getReplayState() == null;
+        return !secondaryConfig.isPresent() || !secondaryConfig.get().isConfigured() ;
     }
     public String disableAct() {
         final boolean notInError =
@@ -43,4 +46,6 @@ public class CommandJdo_exclude {
                 : null;
     }
 
+    @Inject Optional<SecondaryConfig> secondaryConfig;
+
 }
diff --git a/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_openOnPrimary.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_openOnPrimary.java
new file mode 100644
index 0000000..d7f0b61
--- /dev/null
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_openOnPrimary.java
@@ -0,0 +1,54 @@
+package org.apache.isis.extensions.commandreplay.secondary.mixins;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.inject.Inject;
+
+import org.apache.isis.applib.ApplicationException;
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.MemberOrder;
+import org.apache.isis.applib.annotation.SemanticsOf;
+import org.apache.isis.applib.services.bookmark.BookmarkService;
+import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
+import org.apache.isis.extensions.commandreplay.secondary.IsisModuleExtCommandReplaySecondary;
+import org.apache.isis.extensions.commandreplay.secondary.config.SecondaryConfig;
+
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+@Action(
+    semantics = SemanticsOf.SAFE,
+    domainEvent = CommandJdo_openOnPrimary.ActionDomainEvent.class
+)
+@RequiredArgsConstructor
+public class CommandJdo_openOnPrimary<T> {
+
+    public static class ActionDomainEvent
+            extends IsisModuleExtCommandReplaySecondary.ActionDomainEvent<CommandJdo_openOnPrimary> { }
+
+    final CommandJdo commandJdo;
+
+    @MemberOrder(name = "transactionId", sequence = "1")
+    public URL act() {
+        val baseUrlPrefix = lookupBaseUrlPrefix();
+        val urlSuffix = bookmarkService.bookmarkFor(commandJdo).toString();
+
+        try {
+            return new URL(baseUrlPrefix + urlSuffix);
+        } catch (MalformedURLException e) {
+            throw new ApplicationException(e);
+        }
+    }
+    public boolean hideAct() {
+        return !secondaryConfig.isConfigured();
+    }
+
+    private String lookupBaseUrlPrefix() {
+        return secondaryConfig.getPrimaryBaseUrlWicket() + "entity/";
+    }
+
+    @Inject SecondaryConfig secondaryConfig;
+    @Inject BookmarkService bookmarkService;
+
+}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_replayNext.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_replayNext.java
similarity index 73%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_replayNext.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_replayNext.java
index 040595a..00a4283 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/mixins/CommandJdo_replayNext.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_replayNext.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl.mixins;
+package org.apache.isis.extensions.commandreplay.secondary.mixins;
 
 import javax.inject.Inject;
 
@@ -9,34 +9,34 @@ import org.apache.isis.applib.services.command.CommandExecutorService;
 import org.apache.isis.applib.services.message.MessageService;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdoRepository;
-import org.apache.isis.extensions.commandreplay.impl.IsisModuleExtCommandReplayImpl;
-import org.apache.isis.extensions.commandreplay.impl.fetch.PrimaryConfig;
-import org.apache.isis.extensions.commandreplay.impl.StatusException;
+import org.apache.isis.extensions.commandreplay.secondary.IsisModuleExtCommandReplaySecondary;
+import org.apache.isis.extensions.commandreplay.secondary.StatusException;
+import org.apache.isis.extensions.commandreplay.secondary.config.SecondaryConfig;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
-import org.apache.isis.extensions.commandreplay.impl.fetch.CommandFetcher;
-import org.apache.isis.extensions.commandreplay.impl.analysis.CommandReplayAnalysisService;
+import org.apache.isis.extensions.commandreplay.secondary.fetch.CommandFetcher;
+import org.apache.isis.extensions.commandreplay.secondary.analysis.CommandReplayAnalysisService;
 
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 
 @Action(
-        semantics = SemanticsOf.NON_IDEMPOTENT,
-        domainEvent = CommandJdo_replayNext.ActionDomainEvent.class
+    semantics = SemanticsOf.NON_IDEMPOTENT,
+    domainEvent = CommandJdo_replayNext.ActionDomainEvent.class
 )
+@RequiredArgsConstructor
 public class CommandJdo_replayNext {
 
-    public static class ActionDomainEvent extends IsisModuleExtCommandReplayImpl.ActionDomainEvent<CommandJdo_replayNext> { }
+    public static class ActionDomainEvent
+            extends IsisModuleExtCommandReplaySecondary.ActionDomainEvent<CommandJdo_replayNext> { }
 
-    private final CommandJdo commandJdo;
-    public CommandJdo_replayNext(CommandJdo commandJdo) {
-        this.commandJdo = commandJdo;
-    }
+    final CommandJdo commandJdo;
 
     @MemberOrder(name = "executeIn", sequence = "3")
     public CommandJdo act() throws StatusException {
 
         // double check this is still the HWM
-        final CommandJdo replayHwm = commandJdoRepository.findReplayedHwm();
+        final CommandJdo replayHwm = commandJdoRepository.findReplicatedHwm();
         if(commandJdo != replayHwm) {
             messageService.informUser("HWM has changed");
             return replayHwm;
@@ -54,7 +54,6 @@ public class CommandJdo_replayNext {
         return nextHwm;
     }
 
-
     private CommandJdo fetchNext() throws StatusException {
         final CommandDto commandDto = commandFetcher.fetchCommand(this.commandJdo);
         return commandDto == null
@@ -74,8 +73,12 @@ public class CommandJdo_replayNext {
         }
     }
 
+    public boolean hideAct() {
+        return !secondaryConfig.isConfigured();
+    }
+
     public String disableAct() {
-        final CommandJdo replayHwm = commandJdoRepository.findReplayedHwm();
+        final CommandJdo replayHwm = commandJdoRepository.findReplicatedHwm();
 
         if(commandJdo != replayHwm) {
             return "This action can only be performed against the 'HWM' command on the secondary";
@@ -90,17 +93,11 @@ public class CommandJdo_replayNext {
         return null;
     }
 
-    public boolean hideAct() {
-        return !primaryConfig.isConfigured();
-    }
-
 
-    @Inject
-    CommandJdoRepository commandJdoRepository;
+    @Inject CommandJdoRepository commandJdoRepository;
     @Inject CommandFetcher commandFetcher;
     @Inject CommandExecutorService commandExecutorService;
-    @Inject
-    PrimaryConfig primaryConfig;
+    @Inject SecondaryConfig secondaryConfig;
     @Inject MessageService messageService;
     @Inject CommandReplayAnalysisService analysisService;
 }
diff --git a/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_replayQueue.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_replayQueue.java
new file mode 100644
index 0000000..8a31458
--- /dev/null
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/mixins/CommandJdo_replayQueue.java
@@ -0,0 +1,42 @@
+package org.apache.isis.extensions.commandreplay.secondary.mixins;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import org.apache.isis.applib.annotation.Collection;
+import org.apache.isis.applib.annotation.CollectionLayout;
+import org.apache.isis.applib.annotation.MemberOrder;
+import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
+import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdoRepository;
+import org.apache.isis.extensions.commandreplay.secondary.IsisModuleExtCommandReplaySecondary;
+import org.apache.isis.extensions.commandreplay.secondary.config.SecondaryConfig;
+
+import lombok.RequiredArgsConstructor;
+
+@Collection(
+    domainEvent = CommandJdo_replayQueue.CollectionDomainEvent.class
+)
+@CollectionLayout(
+    defaultView = "table"
+)
+@RequiredArgsConstructor
+public class CommandJdo_replayQueue {
+
+    public static class CollectionDomainEvent
+            extends IsisModuleExtCommandReplaySecondary.CollectionDomainEvent<CommandJdo_replayQueue, CommandJdo> { }
+
+    final CommandJdo commandJdo;
+
+    @MemberOrder(sequence = "100.100")
+    public List<CommandJdo> coll() {
+        return commandJdoRepository.findReplayedOnSecondary();
+    }
+    public boolean hideColl() {
+        return !secondaryConfig.isConfigured();
+    }
+
+    @Inject SecondaryConfig secondaryConfig;
+    @Inject CommandJdoRepository commandJdoRepository;
+
+}
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/spi/ReplayCommandExecutionController.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/spi/ReplayCommandExecutionController.java
similarity index 88%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/spi/ReplayCommandExecutionController.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/spi/ReplayCommandExecutionController.java
index c2fec8d..bb2214e 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/spi/ReplayCommandExecutionController.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/spi/ReplayCommandExecutionController.java
@@ -1,4 +1,4 @@
-package org.apache.isis.extensions.commandreplay.impl.spi;
+package org.apache.isis.extensions.commandreplay.secondary.spi;
 
 /**
  * Optional SPI that allows the replicate and replay job to be paused if
diff --git a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/ui/CommandReplayOnSecondaryService.java b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/ui/CommandReplayOnSecondaryService.java
similarity index 74%
rename from extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/ui/CommandReplayOnSecondaryService.java
rename to extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/ui/CommandReplayOnSecondaryService.java
index 629fb3f..8fa72ee 100644
--- a/extensions/core/command-replay/impl/src/main/java/org/apache/isis/extensions/commandreplay/impl/ui/CommandReplayOnSecondaryService.java
+++ b/extensions/core/command-replay/secondary/src/main/java/org/apache/isis/extensions/commandreplay/secondary/ui/CommandReplayOnSecondaryService.java
@@ -1,9 +1,13 @@
-package org.apache.isis.extensions.commandreplay.impl.ui;
+package org.apache.isis.extensions.commandreplay.secondary.ui;
 
 import java.util.Collections;
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.Action;
 import org.apache.isis.applib.annotation.ActionLayout;
@@ -11,12 +15,13 @@ import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.DomainServiceLayout;
 import org.apache.isis.applib.annotation.MemberOrder;
 import org.apache.isis.applib.annotation.NatureOfService;
+import org.apache.isis.applib.annotation.OrderPrecedence;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.services.jaxb.JaxbService;
 import org.apache.isis.applib.value.Clob;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdo;
 import org.apache.isis.extensions.commandlog.impl.jdo.CommandJdoRepository;
-import org.apache.isis.extensions.commandreplay.impl.IsisModuleExtCommandReplayImpl;
+import org.apache.isis.extensions.commandreplay.secondary.IsisModuleExtCommandReplaySecondary;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 import org.apache.isis.schema.cmd.v2.CommandsDto;
 
@@ -24,25 +29,27 @@ import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
 @DomainService(
-        nature = NatureOfService.VIEW,
-        objectType = "isisExtensionsCommandReplay.CommandReplayOnSecondaryService"
+    nature = NatureOfService.VIEW,
+    objectType = "isisExtensionsCommandReplaySecondary.CommandReplayOnSecondaryService"
 )
 @DomainServiceLayout(
-        named = "Activity",
-        menuBar = DomainServiceLayout.MenuBar.SECONDARY
+    named = "Activity",
+    menuBar = DomainServiceLayout.MenuBar.SECONDARY
 )
+@Named("isisExtensionsCommandReplaySecondary.CommandReplayOnSecondaryService")
+@Order(OrderPrecedence.MIDPOINT)
 @Log4j2
 public class CommandReplayOnSecondaryService {
 
     public static abstract class ActionDomainEvent
-            extends IsisModuleExtCommandReplayImpl.ActionDomainEvent<CommandReplayOnSecondaryService> { }
+            extends IsisModuleExtCommandReplaySecondary.ActionDomainEvent<CommandReplayOnSecondaryService> { }
 
     public static class FindReplayHwmOnSecondaryDomainEvent extends ActionDomainEvent { }
     @Action(domainEvent = FindReplayHwmOnSecondaryDomainEvent.class, semantics = SemanticsOf.SAFE)
     @ActionLayout(cssClassFa = "fa-bath")
     @MemberOrder(sequence="60.1")
     public CommandJdo findReplayHwmOnSecondary() {
-        return commandJdoRepository.findReplayedHwm();
+        return commandJdoRepository.findReplicatedHwm();
     }
 
 
diff --git a/extensions/core/command-replay/impl/src/test/java/org/apache/isis/extensions/commandreplay/impl/fetch/ReplicateAndReplayJob_Test.java b/extensions/core/command-replay/secondary/src/test/java/org/apache/isis/extensions/commandreplay/secondary/fetch/CommandFetcher_Test.java
similarity index 81%
rename from extensions/core/command-replay/impl/src/test/java/org/apache/isis/extensions/commandreplay/impl/fetch/ReplicateAndReplayJob_Test.java
rename to extensions/core/command-replay/secondary/src/test/java/org/apache/isis/extensions/commandreplay/secondary/fetch/CommandFetcher_Test.java
index 3b09f03..4fbc55b 100644
--- a/extensions/core/command-replay/impl/src/test/java/org/apache/isis/extensions/commandreplay/impl/fetch/ReplicateAndReplayJob_Test.java
+++ b/extensions/core/command-replay/secondary/src/test/java/org/apache/isis/extensions/commandreplay/secondary/fetch/CommandFetcher_Test.java
@@ -1,14 +1,12 @@
-package org.apache.isis.extensions.commandreplay.impl.fetch;
+package org.apache.isis.extensions.commandreplay.secondary.fetch;
 
 import java.net.URI;
 
 import javax.ws.rs.core.UriBuilder;
 
-
 import org.junit.jupiter.api.Test;
 
 import org.apache.isis.applib.services.jaxb.JaxbService;
-import org.apache.isis.extensions.commandreplay.impl.fetch.CommandFetcher;
 import org.apache.isis.extensions.jaxrsclient.applib.client.JaxRsClient;
 import org.apache.isis.extensions.jaxrsclient.applib.client.JaxRsResponse;
 import org.apache.isis.extensions.jaxrsclient.impl.client.JaxRsClientDefault;
@@ -17,10 +15,10 @@ import org.apache.isis.schema.cmd.v2.CommandsDto;
 import lombok.val;
 
 
-public class ReplicateAndReplayJob_Test {
+public class CommandFetcher_Test {
 
     @Test
-    public void testing_the_unmarshalling() throws Exception {
+    public void testing_the_unmarshalling() {
         val jaxRsClient = new JaxRsClientDefault();
         final UriBuilder uriBuilder = UriBuilder.fromUri(
                         String.format(
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 68addd6..6e9540a 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -76,7 +76,13 @@
 
 			<dependency>
 				<groupId>org.apache.isis.extensions</groupId>
-				<artifactId>isis-extensions-command-replay-impl</artifactId>
+				<artifactId>isis-extensions-command-replay-primary</artifactId>
+				<version>2.0.0-SNAPSHOT</version>
+			</dependency>
+
+			<dependency>
+				<groupId>org.apache.isis.extensions</groupId>
+				<artifactId>isis-extensions-command-replay-secondary</artifactId>
 				<version>2.0.0-SNAPSHOT</version>
 			</dependency>
 
diff --git a/mappings/jaxrsclient/impl/src/main/java/org/apache/isis/extensions/jaxrsclient/impl/client/JaxRsClientDefault.java b/mappings/jaxrsclient/impl/src/main/java/org/apache/isis/extensions/jaxrsclient/impl/client/JaxRsClientDefault.java
index 89a93c8..d4fd32f 100644
--- a/mappings/jaxrsclient/impl/src/main/java/org/apache/isis/extensions/jaxrsclient/impl/client/JaxRsClientDefault.java
+++ b/mappings/jaxrsclient/impl/src/main/java/org/apache/isis/extensions/jaxrsclient/impl/client/JaxRsClientDefault.java
@@ -14,6 +14,9 @@ import javax.ws.rs.core.Response;
 import org.apache.isis.extensions.jaxrsclient.applib.client.JaxRsClient;
 import org.apache.isis.extensions.jaxrsclient.applib.client.JaxRsResponse;
 
+import lombok.Getter;
+import lombok.Setter;
+
 public class JaxRsClientDefault implements JaxRsClient {
 
     protected final ClientBuilder clientBuilder;
@@ -98,12 +101,12 @@ public class JaxRsClientDefault implements JaxRsClient {
     protected void configureInvocationBuilder(final Object invocationBuilder) {
     }
 
-    private static MediaType mediaTypeFor(final Class<?> dtoClass, final ReprType reprType) {
+    private MediaType mediaTypeFor(final Class<?> dtoClass, final ReprType reprType) {
         return mediaTypeFor(dtoClass, reprType.getSuffix());
     }
 
-    private static MediaType mediaTypeFor(final Class<?> dtoClass, final String reprType) {
-        // application/xml;profile="urn:org.restfulobjects:repr-types/action-result";x-ro-domain-type="org.apache.isis.schema.cmd.v1.CommandsDto"
+    private MediaType mediaTypeFor(final Class<?> dtoClass, final String reprType) {
+        // application/xml;profile="urn~org.restfulobjects~repr-types/action-result";x-ro-domain-type="org.apache.isis.schema.cmd.v1.CommandsDto"
         return new MediaType("application", "xml",
                 new HashMap<String,String>() {{
                     put("profile", "urn:org.restfulobjects:repr-types/" + reprType);
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java
index 32b05a2..2b4a126 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/ObjectActionArgHelper.java
@@ -57,9 +57,13 @@ public class ObjectActionArgHelper {
             val paramMeta = parameters.getElseFail(i);
             val paramSpec = paramMeta.getSpecification();
             try {
-                val argAdapter = new JsonParserHelper(resourceContext, paramSpec)
-                        .objectAdapterFor(argRepr);
-                argAdapters.add(_Either.left(argAdapter));
+                if(paramMeta.isOptional() && argRepr == null) {
+                    argAdapters.add(_Either.leftNullable(ManagedObject.empty(paramSpec)));
+                } else {
+                    val argAdapter = new JsonParserHelper(resourceContext, paramSpec)
+                            .objectAdapterFor(argRepr);
+                    argAdapters.add(_Either.left(argAdapter));
+                }
             } catch (Exception e) {
                 
                 val veto = InteractionVeto.actionParamInvalid(