From 439a946b0da90c0c883f1899baa1986b849dc371 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Wed, 16 Apr 2025 15:49:54 +0200
Subject: [PATCH 01/14] OZG-7811 OZG-8098 Rename scopes

---
 .../aggregation/transformation/AggregationMapping.java         | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java
index f32b019..71d00b3 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/transformation/AggregationMapping.java
@@ -28,7 +28,6 @@ import java.util.List;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
-
 import lombok.Builder;
 import lombok.Getter;
 import lombok.Singular;
@@ -57,7 +56,7 @@ public class AggregationMapping {
 	}
 
 	public enum Scope {
-		LAND, MANDANT;
+		EXTERN, INTERN;
 	}
 
 	@Getter
-- 
GitLab


From f5c4a429851caa4f1fe4077ab8430507d103407f Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Thu, 17 Apr 2025 18:45:57 +0200
Subject: [PATCH 02/14] OZG-7811 OZG-8099 Create AggregationDataRemoteService
 and call stub

---
 aggregation-manager-job/pom.xml               |   4 +
 .../AggregationManagerProperties.java         |  43 ++++++
 .../extern/AggregationDataRemoteService.java  |  94 +++++++++++++
 .../extern/GrpcAggregationDataMapper.java     |  38 ++++++
 .../AggregationDataRemoteServiceTest.java     | 128 ++++++++++++++++++
 .../extern/AggregationTestFactory.java        |  47 +++++++
 .../extern/GrpcAggregationDataMapperTest.java |  77 +++++++++++
 .../GrpcAggregationDataTestFactory.java       |  43 ++++++
 8 files changed, 474 insertions(+)
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationTestFactory.java
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapperTest.java
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataTestFactory.java

diff --git a/aggregation-manager-job/pom.xml b/aggregation-manager-job/pom.xml
index f36bfda..3ec206f 100644
--- a/aggregation-manager-job/pom.xml
+++ b/aggregation-manager-job/pom.xml
@@ -50,6 +50,10 @@
 			<artifactId>ozg-cloud-spring-boot-starter</artifactId>
 			<groupId>de.ozgcloud.api-lib</groupId>
 		</dependency>
+		<dependency>
+			<groupId>de.ozgcloud.aggregation</groupId>
+			<artifactId>aggregation-manager-interface</artifactId>
+		</dependency>
 		<!-- Spring -->
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java
new file mode 100644
index 0000000..4ace2f5
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.validation.annotation.Validated;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.log4j.Log4j2;
+
+@ConfigurationProperties(prefix = "ozgcloud")
+@Configuration
+@Validated
+@Getter
+@Setter
+@Log4j2
+public class AggregationManagerProperties {
+
+	private String mandant;
+}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
new file mode 100644
index 0000000..25d4979
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import java.util.Iterator;
+import java.util.stream.Stream;
+
+import de.ozgcloud.aggregation.AggregationManagerProperties;
+import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
+import de.ozgcloud.aggregation.data.GrpcAggregationData;
+import de.ozgcloud.aggregation.data.GrpcSendAggregationDataRequest;
+import de.ozgcloud.aggregation.data.GrpcSendAggregationDataResponse;
+import de.ozgcloud.aggregation.warehouse.DocumentEntry;
+import io.grpc.stub.ClientCallStreamObserver;
+import io.grpc.stub.ClientResponseObserver;
+import lombok.Builder;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+class AggregationDataRemoteService {
+
+	private final AggregationDataServiceGrpc.AggregationDataServiceStub serviceStub;
+	private final AggregationManagerProperties properties;
+	private final GrpcAggregationDataMapper grpcAggregationDataMapper;
+
+	public void sendAggregationData(Aggregation aggregation) {
+		var requestObserver = serviceStub.sendAggregationData(new SendAggregationDataResponseObserver(
+				new RequestData(properties.getMandant(), aggregation.aggregationName,
+						toGrpcAggregationDataStream(aggregation.documentEntries).iterator())));
+	}
+
+	Stream<GrpcAggregationData> toGrpcAggregationDataStream(Stream<DocumentEntry> documentEntries) {
+		return documentEntries.map(grpcAggregationDataMapper::toGrpcAggregationData);
+	}
+
+	@RequiredArgsConstructor
+	static class SendAggregationDataResponseObserver
+			implements ClientResponseObserver<GrpcSendAggregationDataRequest, GrpcSendAggregationDataResponse> {
+
+		private final RequestData requestData;
+
+		@Override
+		public void beforeStart(ClientCallStreamObserver<GrpcSendAggregationDataRequest> requestObserver) {
+			requestObserver.setOnReadyHandler(() -> {
+				while (requestObserver.isReady() && requestData.aggregationDataIterator.hasNext()) {
+					// call onNext()?
+				}
+			});
+		}
+
+		@Override
+		public void onNext(GrpcSendAggregationDataResponse value) {
+
+		}
+
+		@Override
+		public void onError(Throwable t) {
+
+		}
+
+		@Override
+		public void onCompleted() {
+
+		}
+	}
+
+	@Builder
+	public record Aggregation(String aggregationName, Stream<DocumentEntry> documentEntries) {
+	}
+
+	record RequestData(String mandant, String aggregationName, Iterator<GrpcAggregationData> aggregationDataIterator) {
+	}
+}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java
new file mode 100644
index 0000000..d111d82
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import de.ozgcloud.aggregation.data.GrpcAggregationData;
+import de.ozgcloud.aggregation.warehouse.DocumentEntry;
+
+@Mapper
+interface GrpcAggregationDataMapper {
+
+	@Mapping(target = "eingangDatum", source = "eingangsdatum")
+	@Mapping(target = "vorgangName", source = "vorgangsname")
+	GrpcAggregationData toGrpcAggregationData(DocumentEntry documentEntry);
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
new file mode 100644
index 0000000..46e411f
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.aggregation.AggregationManagerProperties;
+import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
+import de.ozgcloud.aggregation.extern.AggregationDataRemoteService.SendAggregationDataResponseObserver;
+import de.ozgcloud.common.test.ReflectionTestUtils;
+
+class AggregationDataRemoteServiceTest {
+
+	@Mock
+	private AggregationDataServiceGrpc.AggregationDataServiceStub serviceStub;
+	@Mock
+	private AggregationManagerProperties properties;
+	@Mock
+	private GrpcAggregationDataMapper grpcAggregationDataMapper;
+	@InjectMocks
+	@Spy
+	private AggregationDataRemoteService service;
+
+	@Nested
+	class TestSendAggregationData {
+
+		private static final String MANDANT = LoremIpsum.getInstance().getWords(1);
+
+		@Captor
+		private ArgumentCaptor<SendAggregationDataResponseObserver> observerCaptor;
+
+		@BeforeEach
+		void init() {
+			when(properties.getMandant()).thenReturn(MANDANT);
+			// only called, after stream was consumed (is lazy)
+			lenient().when(grpcAggregationDataMapper.toGrpcAggregationData(AggregationTestFactory.DOCUMENT_ENTRY)).thenReturn(
+					GrpcAggregationDataTestFactory.create());
+		}
+
+		@Test
+		void shouldGetMandant() {
+			sendAggregationData();
+
+			verify(properties).getMandant();
+		}
+
+		@Test
+		void shouldCallServiceStub() {
+			sendAggregationData();
+
+			verify(serviceStub).sendAggregationData(any(SendAggregationDataResponseObserver.class));
+		}
+
+		@Test
+		void shouldResponseObserverHaveMandant() {
+			sendAggregationData();
+
+			assertThat(getRequestDataFromCapturedResponseObserver().mandant()).isEqualTo(MANDANT);
+		}
+
+		@Test
+		void shouldResponseObserverHaveAggregationName() {
+			sendAggregationData();
+
+			assertThat(getRequestDataFromCapturedResponseObserver().aggregationName()).isEqualTo(AggregationTestFactory.AGGREGATION_NAME);
+		}
+
+		@Test
+		void shouldMapDocumentEntry() {
+			sendAggregationData();
+
+			IteratorUtils.toList(getRequestDataFromCapturedResponseObserver().aggregationDataIterator());
+			verify(grpcAggregationDataMapper).toGrpcAggregationData(AggregationTestFactory.DOCUMENT_ENTRY);
+		}
+
+		@Test
+		@SuppressWarnings("unchecked")
+		void shouldHaveGrpcAggregationData() {
+			sendAggregationData();
+
+			var grpcAggregationDataList = IteratorUtils.toList(getRequestDataFromCapturedResponseObserver().aggregationDataIterator());
+			assertThat(grpcAggregationDataList).containsExactly(GrpcAggregationDataTestFactory.create());
+		}
+
+		private void sendAggregationData() {
+			service.sendAggregationData(AggregationTestFactory.create());
+		}
+
+		private AggregationDataRemoteService.RequestData getRequestDataFromCapturedResponseObserver() {
+			verify(serviceStub).sendAggregationData(observerCaptor.capture());
+			return ReflectionTestUtils.getField(observerCaptor.getValue(), "requestData", AggregationDataRemoteService.RequestData.class);
+		}
+	}
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationTestFactory.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationTestFactory.java
new file mode 100644
index 0000000..6d167dd
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationTestFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import java.util.stream.Stream;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.aggregation.warehouse.DocumentEntry;
+import de.ozgcloud.aggregation.warehouse.DocumentEntryTestFactory;
+
+public class AggregationTestFactory {
+
+	public static final String AGGREGATION_NAME = LoremIpsum.getInstance().getWords(1);
+	public static final DocumentEntry DOCUMENT_ENTRY = DocumentEntryTestFactory.create();
+
+	public static AggregationDataRemoteService.Aggregation create() {
+		return createBuilder().build();
+	}
+
+	public static AggregationDataRemoteService.Aggregation.AggregationBuilder createBuilder() {
+		return AggregationDataRemoteService.Aggregation.builder()
+				.aggregationName(AGGREGATION_NAME)
+				.documentEntries(Stream.of(DOCUMENT_ENTRY));
+	}
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapperTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapperTest.java
new file mode 100644
index 0000000..7f552a7
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapperTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mapstruct.factory.Mappers;
+
+import de.ozgcloud.aggregation.data.GrpcObject;
+import de.ozgcloud.aggregation.warehouse.DocumentEntryTestFactory;
+
+class GrpcAggregationDataMapperTest {
+
+	private final GrpcAggregationDataMapper mapper = Mappers.getMapper(GrpcAggregationDataMapper.class);
+
+	@Nested
+	class TestToGrpcAggregationData {
+
+		@Test
+		void shouldMapId() {
+			var grpcData = mapper.toGrpcAggregationData(DocumentEntryTestFactory.create());
+
+			assertThat(grpcData.getId()).isEqualTo(DocumentEntryTestFactory.ID);
+		}
+
+		@Test
+		void shouldMapStatus() {
+			var grpcData = mapper.toGrpcAggregationData(DocumentEntryTestFactory.create());
+
+			assertThat(grpcData.getStatus()).isEqualTo(DocumentEntryTestFactory.STATUS);
+		}
+
+		@Test
+		void shouldMapEingangDatum() {
+			var grpcData = mapper.toGrpcAggregationData(DocumentEntryTestFactory.create());
+
+			assertThat(grpcData.getEingangDatum()).isEqualTo(DocumentEntryTestFactory.EINGANGSDATUM.toString());
+		}
+
+		@Test
+		void shouldMapVorgangName() {
+			var grpcData = mapper.toGrpcAggregationData(DocumentEntryTestFactory.create());
+
+			assertThat(grpcData.getVorgangName()).isEqualTo(DocumentEntryTestFactory.VORGANG_NAME);
+		}
+
+		@Test
+		void shouldHaveDefaultPayload() {
+			var grpcData = mapper.toGrpcAggregationData(DocumentEntryTestFactory.create());
+
+			assertThat(grpcData.getPayload()).isEqualTo(GrpcObject.getDefaultInstance());
+		}
+	}
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataTestFactory.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataTestFactory.java
new file mode 100644
index 0000000..d3ba13a
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataTestFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import de.ozgcloud.aggregation.data.GrpcAggregationData;
+import de.ozgcloud.aggregation.data.GrpcAggregationData.Builder;
+import de.ozgcloud.aggregation.warehouse.DocumentEntryTestFactory;
+
+public class GrpcAggregationDataTestFactory {
+
+	public static GrpcAggregationData create() {
+		return createBuilder().build();
+	}
+
+	public static Builder createBuilder() {
+		return GrpcAggregationData.newBuilder()
+				.setId(DocumentEntryTestFactory.ID)
+				.setStatus(DocumentEntryTestFactory.STATUS)
+				.setEingangDatum(DocumentEntryTestFactory.EINGANGSDATUM.toString())
+				.setVorgangName(DocumentEntryTestFactory.VORGANG_NAME);
+	}
+}
-- 
GitLab


From 5c7df16827d3cc5f809f5945c900e4ae9b5356c8 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Wed, 23 Apr 2025 19:20:27 +0200
Subject: [PATCH 03/14] OZG-7811 OZG-8099 Implement sendAggregationData

---
 .../extern/AggregationDataRemoteService.java  |  67 ++-
 .../AggregationDataRemoteServiceTest.java     | 383 ++++++++++++++++--
 2 files changed, 412 insertions(+), 38 deletions(-)

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
index 25d4979..547475d 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
@@ -24,6 +24,8 @@
 package de.ozgcloud.aggregation.extern;
 
 import java.util.Iterator;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
 import java.util.stream.Stream;
 
 import de.ozgcloud.aggregation.AggregationManagerProperties;
@@ -35,19 +37,28 @@ import de.ozgcloud.aggregation.warehouse.DocumentEntry;
 import io.grpc.stub.ClientCallStreamObserver;
 import io.grpc.stub.ClientResponseObserver;
 import lombok.Builder;
+import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
 @RequiredArgsConstructor
 class AggregationDataRemoteService {
 
+	static final int BATCH_SIZE = 100;
+
 	private final AggregationDataServiceGrpc.AggregationDataServiceStub serviceStub;
 	private final AggregationManagerProperties properties;
 	private final GrpcAggregationDataMapper grpcAggregationDataMapper;
 
-	public void sendAggregationData(Aggregation aggregation) {
-		var requestObserver = serviceStub.sendAggregationData(new SendAggregationDataResponseObserver(
-				new RequestData(properties.getMandant(), aggregation.aggregationName,
-						toGrpcAggregationDataStream(aggregation.documentEntries).iterator())));
+	public Future<Void> sendAggregationData(Aggregation aggregation) {
+		var responseObserver = buildSendAggregationDataResponseObserver(aggregation);
+		serviceStub.sendAggregationData(responseObserver);
+		return responseObserver.getResponseFuture();
+	}
+
+	SendAggregationDataResponseObserver buildSendAggregationDataResponseObserver(Aggregation aggregation) {
+		var requestData = new RequestData(properties.getMandant(), aggregation.aggregationName,
+				toGrpcAggregationDataStream(aggregation.documentEntries).iterator());
+		return new SendAggregationDataResponseObserver(requestData);
 	}
 
 	Stream<GrpcAggregationData> toGrpcAggregationDataStream(Stream<DocumentEntry> documentEntries) {
@@ -59,14 +70,12 @@ class AggregationDataRemoteService {
 			implements ClientResponseObserver<GrpcSendAggregationDataRequest, GrpcSendAggregationDataResponse> {
 
 		private final RequestData requestData;
+		@Getter
+		private final CompletableFuture<Void> responseFuture = new CompletableFuture<>();
 
 		@Override
 		public void beforeStart(ClientCallStreamObserver<GrpcSendAggregationDataRequest> requestObserver) {
-			requestObserver.setOnReadyHandler(() -> {
-				while (requestObserver.isReady() && requestData.aggregationDataIterator.hasNext()) {
-					// call onNext()?
-				}
-			});
+			requestObserver.setOnReadyHandler(buildOnReadyHandler(requestObserver));
 		}
 
 		@Override
@@ -76,12 +85,49 @@ class AggregationDataRemoteService {
 
 		@Override
 		public void onError(Throwable t) {
-
+			responseFuture.completeExceptionally(t);
 		}
 
 		@Override
 		public void onCompleted() {
+			responseFuture.complete(null);
+		}
+
+		SendAggregationDataOnReadyHandler buildOnReadyHandler(ClientCallStreamObserver<GrpcSendAggregationDataRequest> requestObserver) {
+			return new SendAggregationDataOnReadyHandler(requestObserver, BATCH_SIZE, requestData);
+		}
+	}
+
+	@RequiredArgsConstructor
+	static class SendAggregationDataOnReadyHandler implements Runnable {
+
+		private final ClientCallStreamObserver<GrpcSendAggregationDataRequest> requestObserver;
+		private final int batchSize;
+		private final RequestData requestData;
 
+		@Override
+		public void run() {
+			while (requestObserver.isReady() && requestData.aggregationDataIterator.hasNext()) {
+				requestObserver.onNext(buildRequest());
+			}
+			if (!requestData.aggregationDataIterator.hasNext()) {
+				requestObserver.onCompleted();
+			}
+		}
+
+		GrpcSendAggregationDataRequest buildRequest() {
+			var builder = GrpcSendAggregationDataRequest.newBuilder()
+					.setName(requestData.aggregationName())
+					.setMandant(requestData.mandant());
+			addAggregationData(builder);
+			return builder.build();
+		}
+
+		private void addAggregationData(GrpcSendAggregationDataRequest.Builder builder) {
+			var elementsAdded = 0;
+			while (requestData.aggregationDataIterator.hasNext() && elementsAdded++ < batchSize) {
+				builder.addAggregationData(requestData.aggregationDataIterator.next());
+			}
 		}
 	}
 
@@ -89,6 +135,7 @@ class AggregationDataRemoteService {
 	public record Aggregation(String aggregationName, Stream<DocumentEntry> documentEntries) {
 	}
 
+	@Builder
 	record RequestData(String mandant, String aggregationName, Iterator<GrpcAggregationData> aggregationDataIterator) {
 	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
index 46e411f..7836cdf 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
@@ -26,6 +26,12 @@ package de.ozgcloud.aggregation.extern;
 import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
 import org.apache.commons.collections.IteratorUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
@@ -40,8 +46,13 @@ import com.thedeanda.lorem.LoremIpsum;
 
 import de.ozgcloud.aggregation.AggregationManagerProperties;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
+import de.ozgcloud.aggregation.data.GrpcAggregationData;
+import de.ozgcloud.aggregation.data.GrpcSendAggregationDataRequest;
 import de.ozgcloud.aggregation.extern.AggregationDataRemoteService.SendAggregationDataResponseObserver;
+import de.ozgcloud.aggregation.warehouse.DocumentEntry;
 import de.ozgcloud.common.test.ReflectionTestUtils;
+import io.grpc.stub.ClientCallStreamObserver;
+import lombok.SneakyThrows;
 
 class AggregationDataRemoteServiceTest {
 
@@ -58,71 +69,387 @@ class AggregationDataRemoteServiceTest {
 	@Nested
 	class TestSendAggregationData {
 
-		private static final String MANDANT = LoremIpsum.getInstance().getWords(1);
+		private final AggregationDataRemoteService.Aggregation aggregation = AggregationTestFactory.create();
 
-		@Captor
-		private ArgumentCaptor<SendAggregationDataResponseObserver> observerCaptor;
+		@Mock
+		private SendAggregationDataResponseObserver responseObserver;
+		@Mock
+		private CompletableFuture<Void> responseFuture;
 
 		@BeforeEach
 		void init() {
-			when(properties.getMandant()).thenReturn(MANDANT);
-			// only called, after stream was consumed (is lazy)
-			lenient().when(grpcAggregationDataMapper.toGrpcAggregationData(AggregationTestFactory.DOCUMENT_ENTRY)).thenReturn(
-					GrpcAggregationDataTestFactory.create());
+			doReturn(responseObserver).when(service).buildSendAggregationDataResponseObserver(any());
+			when(responseObserver.getResponseFuture()).thenReturn(responseFuture);
 		}
 
 		@Test
-		void shouldGetMandant() {
+		void shouldBuildResponseObserver() {
 			sendAggregationData();
 
-			verify(properties).getMandant();
+			verify(service).buildSendAggregationDataResponseObserver(aggregation);
 		}
 
 		@Test
 		void shouldCallServiceStub() {
 			sendAggregationData();
 
-			verify(serviceStub).sendAggregationData(any(SendAggregationDataResponseObserver.class));
+			verify(serviceStub).sendAggregationData(responseObserver);
 		}
 
 		@Test
-		void shouldResponseObserverHaveMandant() {
-			sendAggregationData();
+		void shouldReturnFutureFromResponseObserver() {
+			var future = sendAggregationData();
 
-			assertThat(getRequestDataFromCapturedResponseObserver().mandant()).isEqualTo(MANDANT);
+			assertThat(future).isSameAs(responseFuture);
+		}
+
+		private Future<Void> sendAggregationData() {
+			return service.sendAggregationData(aggregation);
+		}
+	}
+
+	@Nested
+	class TestBuildSendAggregationDataResponseObserver {
+
+		@Captor
+		private ArgumentCaptor<Stream<DocumentEntry>> documentEntriesCaptor;
+
+		@BeforeEach
+		void init() {
+			doReturn(Stream.of(GrpcAggregationDataTestFactory.create())).when(service).toGrpcAggregationDataStream(any());
 		}
 
 		@Test
-		void shouldResponseObserverHaveAggregationName() {
-			sendAggregationData();
+		void shouldGetMandant() {
+			service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
+
+			verify(properties).getMandant();
+		}
+
+		@Test
+		void shouldCreateGrpcAggregationDataStream() {
+			service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
 
-			assertThat(getRequestDataFromCapturedResponseObserver().aggregationName()).isEqualTo(AggregationTestFactory.AGGREGATION_NAME);
+			verify(service).toGrpcAggregationDataStream(documentEntriesCaptor.capture());
+			assertThat(documentEntriesCaptor.getValue()).containsExactly(AggregationTestFactory.DOCUMENT_ENTRY);
+		}
+
+		@Test
+		void shouldReturnResponseObserver() {
+			var builtResponseObserver = service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
+
+			assertThat(builtResponseObserver).isNotNull();
+		}
+
+		@Nested
+		class TestBuiltResponseObserver {
+
+			private static final String MANDANT = LoremIpsum.getInstance().getWords(1);
+
+			@BeforeEach
+			void init() {
+				when(properties.getMandant()).thenReturn(MANDANT);
+			}
+
+			@Test
+			void shouldHaveMandant() {
+				var builtResponseObserver = service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
+
+				assertThat(getRequestData(builtResponseObserver).mandant()).isEqualTo(MANDANT);
+			}
+
+			@Test
+			void shouldHaveAggregationName() {
+				var builtResponseObserver = service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
+
+				assertThat(getRequestData(builtResponseObserver).aggregationName()).isEqualTo(AggregationTestFactory.AGGREGATION_NAME);
+			}
+
+			@Test
+			@SuppressWarnings("unchecked")
+			void shouldHaveGrpcAggregationData() {
+				var builtResponseObserver = service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
+
+				var aggregationData = IteratorUtils.toList(getRequestData(builtResponseObserver).aggregationDataIterator());
+				assertThat(aggregationData).containsExactly(GrpcAggregationDataTestFactory.create());
+			}
+
+			private AggregationDataRemoteService.RequestData getRequestData(SendAggregationDataResponseObserver responseObserver) {
+				return ReflectionTestUtils.getField(responseObserver, "requestData", AggregationDataRemoteService.RequestData.class);
+			}
+		}
+	}
+
+	@Nested
+	class TestToGrpcAggregationDataStream {
+
+		@BeforeEach
+		void init() {
+			when(grpcAggregationDataMapper.toGrpcAggregationData(AggregationTestFactory.DOCUMENT_ENTRY)).thenReturn(
+					GrpcAggregationDataTestFactory.create());
 		}
 
 		@Test
 		void shouldMapDocumentEntry() {
-			sendAggregationData();
+			service.toGrpcAggregationDataStream(Stream.of(AggregationTestFactory.DOCUMENT_ENTRY)).toList();
 
-			IteratorUtils.toList(getRequestDataFromCapturedResponseObserver().aggregationDataIterator());
 			verify(grpcAggregationDataMapper).toGrpcAggregationData(AggregationTestFactory.DOCUMENT_ENTRY);
 		}
 
 		@Test
-		@SuppressWarnings("unchecked")
-		void shouldHaveGrpcAggregationData() {
-			sendAggregationData();
+		void shouldReturnMappedDocumentEntries() {
+			var mappedDocumentEntries = service.toGrpcAggregationDataStream(Stream.of(AggregationTestFactory.DOCUMENT_ENTRY));
+
+			assertThat(mappedDocumentEntries.toList()).containsExactly(GrpcAggregationDataTestFactory.create());
+		}
+	}
+
+	@Nested
+	class SendAggregationDataResponseObserverTest {
+
+		@Mock
+		private AggregationDataRemoteService.RequestData requestData;
+		@Spy
+		@InjectMocks
+		private SendAggregationDataResponseObserver responseObserver;
+
+		@Nested
+		class TestBeforeStart {
+
+			@Mock
+			private ClientCallStreamObserver<GrpcSendAggregationDataRequest> requestObserver;
+			@Mock
+			private AggregationDataRemoteService.SendAggregationDataOnReadyHandler onReadyHandler;
+
+			@BeforeEach
+			void init() {
+				doReturn(onReadyHandler).when(responseObserver).buildOnReadyHandler(any());
+			}
+
+			@Test
+			void shouldBuildOnReadyHandler() {
+				responseObserver.beforeStart(requestObserver);
+
+				verify(responseObserver).buildOnReadyHandler(requestObserver);
+			}
+
+			@Test
+			void shouldSetOnReadyHandler() {
+				responseObserver.beforeStart(requestObserver);
+
+				verify(requestObserver).setOnReadyHandler(onReadyHandler);
+			}
+		}
+
+		@Nested
+		class TestOnError {
+
+			@Test
+			@SneakyThrows
+			void shouldCompleteExceptionally() {
+				var error = new Throwable();
+
+				responseObserver.onError(error);
+
+				assertThatException().isThrownBy(() -> responseObserver.getResponseFuture().get()).withCause(error);
+			}
+		}
+
+		@Nested
+		class TestOnCompleted {
+
+			@Test
+			void shouldCompleteFuture() {
+				responseObserver.onCompleted();
+
+				assertThat(responseObserver.getResponseFuture().isDone()).isTrue();
+			}
+		}
+	}
+
+	@Nested
+	class SendAggregationDataOnReadyHandlerTest {
+
+		private static final int BATCH_SIZE = 2;
+		private static final String MANDANT = LoremIpsum.getInstance().getWords(1);
+
+		@Mock
+		private ClientCallStreamObserver<GrpcSendAggregationDataRequest> requestObserver;
+
+		@Nested
+		class TestRun {
+
+			@Mock
+			private GrpcSendAggregationDataRequest request;
+
+			@Test
+			void shouldCheckIfStreamIsReady() {
+				createOnReadyHandler(createAggregationData(1)).run();
+
+				verify(requestObserver).isReady();
+			}
+
+			@Test
+			void shouldNotCallOnNextWhenStreamIsNotReady() {
+				when(requestObserver.isReady()).thenReturn(false);
+
+				createOnReadyHandler(createAggregationData(1)).run();
+
+				verify(requestObserver, never()).onNext(any());
+			}
+
+			@Test
+			void shouldNotCallOnNextWhenHasNoMoreData() {
+				when(requestObserver.isReady()).thenReturn(true);
+
+				createOnReadyHandler(createAggregationData(0)).run();
+
+				verify(requestObserver, never()).onNext(any());
+			}
+
+			@Test
+			void shouldBuildRequest() {
+				when(requestObserver.isReady()).thenReturn(true);
+				var onReadyHandler = spy(createOnReadyHandler(createAggregationData(1)));
+
+				onReadyHandler.run();
+
+				verify(onReadyHandler).buildRequest();
+			}
+
+			@Test
+			void shouldCallOnNextWithBuiltRequest() {
+				when(requestObserver.isReady()).thenReturn(true).thenReturn(false);
+				var onReadyHandler = spy(createOnReadyHandler(createAggregationData(1)));
+				doReturn(request).when(onReadyHandler).buildRequest();
+
+				onReadyHandler.run();
+
+				verify(requestObserver).onNext(request);
+			}
+
+			@Test
+			void shouldCallOnNextUntilAllDataWasSent() {
+				when(requestObserver.isReady()).thenReturn(true);
+
+				createOnReadyHandler(createAggregationData(BATCH_SIZE + 1)).run();
+
+				verify(requestObserver, times(2)).onNext(any());
+			}
+
+			@Test
+			void shouldCompleteRequest() {
+				when(requestObserver.isReady()).thenReturn(true);
+
+				createOnReadyHandler(createAggregationData(BATCH_SIZE + 1)).run();
+
+				verify(requestObserver).onCompleted();
+			}
+
+			@Test
+			void shouldNotCompleteRequestIfNotAllDataWasSent() {
+				when(requestObserver.isReady()).thenReturn(true).thenReturn(false);
+
+				createOnReadyHandler(createAggregationData(BATCH_SIZE + 1)).run();
+
+				verify(requestObserver, never()).onCompleted();
+			}
+		}
+
+		@Nested
+		class TestBuildRequest {
+
+			@Test
+			void shouldSetName() {
+				var request = createOnReadyHandler().buildRequest();
+
+				assertThat(request.getName()).isEqualTo(AggregationTestFactory.AGGREGATION_NAME);
+			}
+
+			@Test
+			void shouldSetMandant() {
+				var request = createOnReadyHandler().buildRequest();
+
+				assertThat(request.getMandant()).isEqualTo(MANDANT);
+			}
+
+			@Nested
+			class OnHasNoAggregationData {
+
+				@Test
+				void shouldAggregationDataBeEmpty() {
+					var request = createOnReadyHandler(List.of()).buildRequest();
+
+					assertThat(request.getAggregationDataCount()).isEqualTo(0);
+				}
+			}
+
+			@Nested
+			class OnHasLessAggregationDataThenBatchSize {
+
+				@Test
+				void shouldAddAllAggregationData() {
+					var aggregationData = createAggregationData(BATCH_SIZE - 1);
+
+					var request = createOnReadyHandler(aggregationData).buildRequest();
+
+					assertThat(request.getAggregationDataList()).hasSameElementsAs(aggregationData);
+				}
+			}
+
+			@Nested
+			class OnHasBatchSizeOfAggregationData {
+
+				@Test
+				void shouldAddAllAggregationData() {
+					var aggregationData = createAggregationData(BATCH_SIZE);
+
+					var request = createOnReadyHandler(aggregationData).buildRequest();
+
+					assertThat(request.getAggregationDataList()).hasSameElementsAs(aggregationData);
+				}
+			}
+
+			@Nested
+			class OnHasMoreAggregationDataThenBatchSize {
+
+				@Test
+				void shouldHaveExactlyBatchSizeOfAggregationData() {
+					var aggregationData = createAggregationData(BATCH_SIZE + 1);
+
+					var request = createOnReadyHandler(aggregationData).buildRequest();
+
+					assertThat(request.getAggregationDataCount()).isEqualTo(BATCH_SIZE);
+				}
+
+				@Test
+				void shouldContainOnlyFirstBatchSizeOfElements() {
+					var aggregationData = createAggregationData(BATCH_SIZE + 1);
+
+					var request = createOnReadyHandler(aggregationData).buildRequest();
+
+					assertThat(request.getAggregationDataList()).containsExactlyElementsOf(aggregationData.subList(0, BATCH_SIZE));
+				}
+			}
+		}
 
-			var grpcAggregationDataList = IteratorUtils.toList(getRequestDataFromCapturedResponseObserver().aggregationDataIterator());
-			assertThat(grpcAggregationDataList).containsExactly(GrpcAggregationDataTestFactory.create());
+		private List<GrpcAggregationData> createAggregationData(int count) {
+			return IntStream.range(1, count + 1)
+					.mapToObj(idx -> GrpcAggregationDataTestFactory.createBuilder().setVorgangName("vorgang " + idx).build())
+					.toList();
 		}
 
-		private void sendAggregationData() {
-			service.sendAggregationData(AggregationTestFactory.create());
+		private AggregationDataRemoteService.SendAggregationDataOnReadyHandler createOnReadyHandler() {
+			return createOnReadyHandler(List.of());
 		}
 
-		private AggregationDataRemoteService.RequestData getRequestDataFromCapturedResponseObserver() {
-			verify(serviceStub).sendAggregationData(observerCaptor.capture());
-			return ReflectionTestUtils.getField(observerCaptor.getValue(), "requestData", AggregationDataRemoteService.RequestData.class);
+		private AggregationDataRemoteService.SendAggregationDataOnReadyHandler createOnReadyHandler(List<GrpcAggregationData> aggregationData) {
+			return new AggregationDataRemoteService.SendAggregationDataOnReadyHandler(requestObserver, BATCH_SIZE,
+					AggregationDataRemoteService.RequestData.builder()
+							.mandant(MANDANT)
+							.aggregationName(AggregationTestFactory.AGGREGATION_NAME)
+							.aggregationDataIterator(aggregationData.iterator())
+							.build());
 		}
 	}
 }
-- 
GitLab


From 65efb02609c4ded027921fd9c9882cf8fddfd4ae Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Thu, 24 Apr 2025 09:20:59 +0200
Subject: [PATCH 04/14] OZG-7811 OZG-8099 AggregationDataRemoteService
 conditional on property

---
 .../aggregation/extern/AggregationDataRemoteService.java   | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
index 547475d..c10dbbb 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
@@ -28,6 +28,9 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 import java.util.stream.Stream;
 
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Service;
+
 import de.ozgcloud.aggregation.AggregationManagerProperties;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
 import de.ozgcloud.aggregation.data.GrpcAggregationData;
@@ -39,12 +42,16 @@ import io.grpc.stub.ClientResponseObserver;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import net.devh.boot.grpc.client.inject.GrpcClient;
 
+@Service
+@ConditionalOnProperty("grpc.client.aggregation-manager.address")
 @RequiredArgsConstructor
 class AggregationDataRemoteService {
 
 	static final int BATCH_SIZE = 100;
 
+	@GrpcClient("aggregation-manager")
 	private final AggregationDataServiceGrpc.AggregationDataServiceStub serviceStub;
 	private final AggregationManagerProperties properties;
 	private final GrpcAggregationDataMapper grpcAggregationDataMapper;
-- 
GitLab


From 81f57c65ce3a4b5f6df6255ffd0d5c8a1448b0d2 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Thu, 24 Apr 2025 14:56:00 +0200
Subject: [PATCH 05/14] OZG-7811 OZG-8099 Read batch size from configuration

---
 .../extern/AggregationDataRemoteService.java  |  9 +++---
 .../AggregationDataRemoteServiceTest.java     | 29 +++++++++++++++++--
 2 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
index c10dbbb..643bbeb 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
@@ -31,6 +31,7 @@ import java.util.stream.Stream;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
+import de.ozgcloud.aggregation.AggregationManagerConfiguration;
 import de.ozgcloud.aggregation.AggregationManagerProperties;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
 import de.ozgcloud.aggregation.data.GrpcAggregationData;
@@ -49,11 +50,10 @@ import net.devh.boot.grpc.client.inject.GrpcClient;
 @RequiredArgsConstructor
 class AggregationDataRemoteService {
 
-	static final int BATCH_SIZE = 100;
-
 	@GrpcClient("aggregation-manager")
 	private final AggregationDataServiceGrpc.AggregationDataServiceStub serviceStub;
 	private final AggregationManagerProperties properties;
+	private final AggregationManagerConfiguration configuration;
 	private final GrpcAggregationDataMapper grpcAggregationDataMapper;
 
 	public Future<Void> sendAggregationData(Aggregation aggregation) {
@@ -65,7 +65,7 @@ class AggregationDataRemoteService {
 	SendAggregationDataResponseObserver buildSendAggregationDataResponseObserver(Aggregation aggregation) {
 		var requestData = new RequestData(properties.getMandant(), aggregation.aggregationName,
 				toGrpcAggregationDataStream(aggregation.documentEntries).iterator());
-		return new SendAggregationDataResponseObserver(requestData);
+		return new SendAggregationDataResponseObserver(configuration.getFetchingBatchSize(), requestData);
 	}
 
 	Stream<GrpcAggregationData> toGrpcAggregationDataStream(Stream<DocumentEntry> documentEntries) {
@@ -76,6 +76,7 @@ class AggregationDataRemoteService {
 	static class SendAggregationDataResponseObserver
 			implements ClientResponseObserver<GrpcSendAggregationDataRequest, GrpcSendAggregationDataResponse> {
 
+		private final int batchSize;
 		private final RequestData requestData;
 		@Getter
 		private final CompletableFuture<Void> responseFuture = new CompletableFuture<>();
@@ -101,7 +102,7 @@ class AggregationDataRemoteService {
 		}
 
 		SendAggregationDataOnReadyHandler buildOnReadyHandler(ClientCallStreamObserver<GrpcSendAggregationDataRequest> requestObserver) {
-			return new SendAggregationDataOnReadyHandler(requestObserver, BATCH_SIZE, requestData);
+			return new SendAggregationDataOnReadyHandler(requestObserver, batchSize, requestData);
 		}
 	}
 
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
index 7836cdf..2023e0c 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
@@ -27,6 +27,7 @@ import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
 import java.util.List;
+import java.util.Random;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 import java.util.stream.IntStream;
@@ -44,6 +45,7 @@ import org.mockito.Spy;
 
 import com.thedeanda.lorem.LoremIpsum;
 
+import de.ozgcloud.aggregation.AggregationManagerConfiguration;
 import de.ozgcloud.aggregation.AggregationManagerProperties;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
 import de.ozgcloud.aggregation.data.GrpcAggregationData;
@@ -61,6 +63,8 @@ class AggregationDataRemoteServiceTest {
 	@Mock
 	private AggregationManagerProperties properties;
 	@Mock
+	private AggregationManagerConfiguration configuration;
+	@Mock
 	private GrpcAggregationDataMapper grpcAggregationDataMapper;
 	@InjectMocks
 	@Spy
@@ -111,12 +115,14 @@ class AggregationDataRemoteServiceTest {
 	@Nested
 	class TestBuildSendAggregationDataResponseObserver {
 
+		private static final int BATCH_SIZE = new Random().nextInt(100);
 		@Captor
 		private ArgumentCaptor<Stream<DocumentEntry>> documentEntriesCaptor;
 
 		@BeforeEach
 		void init() {
 			doReturn(Stream.of(GrpcAggregationDataTestFactory.create())).when(service).toGrpcAggregationDataStream(any());
+			when(configuration.getFetchingBatchSize()).thenReturn(BATCH_SIZE);
 		}
 
 		@Test
@@ -134,6 +140,13 @@ class AggregationDataRemoteServiceTest {
 			assertThat(documentEntriesCaptor.getValue()).containsExactly(AggregationTestFactory.DOCUMENT_ENTRY);
 		}
 
+		@Test
+		void shouldGetBatchSize() {
+			service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
+
+			verify(configuration).getFetchingBatchSize();
+		}
+
 		@Test
 		void shouldReturnResponseObserver() {
 			var builtResponseObserver = service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
@@ -151,6 +164,13 @@ class AggregationDataRemoteServiceTest {
 				when(properties.getMandant()).thenReturn(MANDANT);
 			}
 
+			@Test
+			void shouldHaveBatchSize() {
+				var builtResponseObserver = service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
+
+				assertThat(getBatchSize(builtResponseObserver)).isEqualTo(BATCH_SIZE);
+			}
+
 			@Test
 			void shouldHaveMandant() {
 				var builtResponseObserver = service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
@@ -174,6 +194,10 @@ class AggregationDataRemoteServiceTest {
 				assertThat(aggregationData).containsExactly(GrpcAggregationDataTestFactory.create());
 			}
 
+			private int getBatchSize(SendAggregationDataResponseObserver responseObserver) {
+				return ReflectionTestUtils.getField(responseObserver, "batchSize", Integer.class);
+			}
+
 			private AggregationDataRemoteService.RequestData getRequestData(SendAggregationDataResponseObserver responseObserver) {
 				return ReflectionTestUtils.getField(responseObserver, "requestData", AggregationDataRemoteService.RequestData.class);
 			}
@@ -207,11 +231,12 @@ class AggregationDataRemoteServiceTest {
 	@Nested
 	class SendAggregationDataResponseObserverTest {
 
+		private static final int BATCH_SIZE = 2;
+
 		@Mock
 		private AggregationDataRemoteService.RequestData requestData;
 		@Spy
-		@InjectMocks
-		private SendAggregationDataResponseObserver responseObserver;
+		private SendAggregationDataResponseObserver responseObserver = new SendAggregationDataResponseObserver(BATCH_SIZE, requestData);
 
 		@Nested
 		class TestBeforeStart {
-- 
GitLab


From 39eece3af9e00603b135d530dc5e12a85b6ae2a9 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Thu, 24 Apr 2025 19:09:57 +0200
Subject: [PATCH 06/14] OZG-7811 OZG-8099 Use loaders in Aggregator

---
 .../de/ozgcloud/aggregation/Aggregation.java  |  33 +++++
 .../aggregation/AggregationDataLoader.java    |  29 ++++
 .../AggregationWarehouseDataLoader.java       |  53 ++++++++
 .../de/ozgcloud/aggregation/Aggregator.java   |  28 ++--
 .../extern/AggregationDataRemoteService.java  |   9 +-
 .../extern/AggregationRemoteDataLoader.java   |  56 ++++++++
 .../warehouse/CustomWarehouseRepository.java  |   4 +-
 .../CustomWarehouseRepositoryImpl.java        |   7 +-
 .../{extern => }/AggregationTestFactory.java  |   8 +-
 .../AggregationWarehouseDataLoaderTest.java   | 110 +++++++++++++++
 .../ozgcloud/aggregation/AggregatorTest.java  |  87 ++++++------
 .../AggregationDataRemoteServiceTest.java     |   4 +-
 .../AggregationRemoteDataLoaderTest.java      | 127 ++++++++++++++++++
 .../CustomWarehouseRepositoryImplTest.java    |  13 +-
 14 files changed, 485 insertions(+), 83 deletions(-)
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregation.java
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
 rename aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/{extern => }/AggregationTestFactory.java (85%)
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregation.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregation.java
new file mode 100644
index 0000000..50368c6
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregation.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation;
+
+import java.util.stream.Stream;
+
+import de.ozgcloud.aggregation.warehouse.DocumentEntry;
+import lombok.Builder;
+
+@Builder
+public record Aggregation(String aggregationName, Stream<DocumentEntry> documentEntries) {
+}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
new file mode 100644
index 0000000..0bd3bd6
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation;
+
+public interface AggregationDataLoader {
+
+	void loadIntoTarget(Aggregation aggregation);
+}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
new file mode 100644
index 0000000..1873800
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.aggregation.warehouse.DocumentEntry;
+import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Component
+@Scope("prototype")
+@RequiredArgsConstructor
+@Log4j2
+class AggregationWarehouseDataLoader implements AggregationDataLoader {
+
+	private final WarehouseRepository repository;
+
+	@Override
+	public void loadIntoTarget(Aggregation aggregation) {
+		var collectionName = getCollectionName(aggregation);
+		repository.clearCollection(collectionName);
+		repository.saveAllInCollection(aggregation.documentEntries(), collectionName);
+	}
+
+	String getCollectionName(Aggregation aggregation) {
+		return StringUtils.isNotBlank(aggregation.aggregationName()) ? aggregation.aggregationName() : DocumentEntry.COLLECTION;
+	}
+}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregator.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregator.java
index 79cf481..3049875 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregator.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/Aggregator.java
@@ -22,7 +22,6 @@ import de.ozgcloud.aggregation.transformation.AggregationMapping.FormIdentifier;
 import de.ozgcloud.aggregation.transformation.TransformationException;
 import de.ozgcloud.aggregation.transformation.VorgangMapper;
 import de.ozgcloud.aggregation.warehouse.DocumentEntry;
-import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
 import de.ozgcloud.apilib.vorgang.OzgCloudVorgang;
 import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery;
 import de.ozgcloud.apilib.vorgang.OzgCloudVorgangQuery.FormIdentification;
@@ -42,13 +41,13 @@ class Aggregator {
 	private static final Predicate<Batch> HAS_NEXT_BATCH = x -> !x.items.isEmpty();
 
 	private final OzgCloudVorgangService vorgangService;
-	private final WarehouseRepository repository;
 	private final VorgangMapper vorgangMapper;
 
 	private Execution execution;
 	private FormIdentifier formIdentifier;
-	private String collectionName = DocumentEntry.COLLECTION;
+	private String aggregationName;
 	private int batchSize = 100;
+	private AggregationDataLoader loader;
 
 	public Aggregator withExecution(Execution execution) {
 		this.execution = execution;
@@ -58,7 +57,7 @@ class Aggregator {
 	public Aggregator withAggregationMapping(AggregationMapping aggregationMapping) {
 		if (Objects.nonNull(aggregationMapping)) {
 			this.formIdentifier = aggregationMapping.getFormIdentifier();
-			this.collectionName = aggregationMapping.getName();
+			this.aggregationName = aggregationMapping.getName();
 		}
 		return this;
 	}
@@ -68,8 +67,13 @@ class Aggregator {
 		return this;
 	}
 
+	public Aggregator withLoader(AggregationDataLoader loader) {
+		this.loader = loader;
+		return this;
+	}
+
 	public void aggregate() {
-		loadVorgaengeIntoRepository(Stream.concat(extractBatchesOfVorgaengeFromDataSource(), extractBatchesOfDeletedVorgaengeFromDataSource()));
+		loadVorgaenge(Stream.concat(extractBatchesOfVorgaengeFromDataSource(), extractBatchesOfDeletedVorgaengeFromDataSource()));
 	}
 
 	Stream<Batch> extractBatchesOfVorgaengeFromDataSource() {
@@ -129,13 +133,12 @@ class Aggregator {
 		return UUID.randomUUID();
 	}
 
-	void loadVorgaengeIntoRepository(Stream<Batch> batches) {
-		repository.clearCollection(collectionName);
-		batches.map(this::transformBatchToDocumentEntries).forEach(this::loadDocumentEntriesIntoRepository);
+	void loadVorgaenge(Stream<Batch> batches) {
+		loader.loadIntoTarget(new Aggregation(aggregationName, batches.flatMap(this::transformBatchToDocumentEntries)));
 	}
 
-	List<DocumentEntry> transformBatchToDocumentEntries(Batch batch) {
-		return batch.items.stream().map(vorgang -> transformWithinBatch(batch, vorgang)).filter(Objects::nonNull).toList();
+	Stream<DocumentEntry> transformBatchToDocumentEntries(Batch batch) {
+		return batch.items.stream().map(vorgang -> transformWithinBatch(batch, vorgang)).filter(Objects::nonNull);
 	}
 
 	DocumentEntry transformWithinBatch(Batch batch, OzgCloudVorgang vorgang) {
@@ -149,11 +152,6 @@ class Aggregator {
 		}
 	}
 
-	void loadDocumentEntriesIntoRepository(List<DocumentEntry> entries) {
-		LOG.atDebug().log("store documents: {}", () -> entries.stream().map(DocumentEntry::getId).toList());
-		repository.saveAllInCollection(entries, collectionName);
-	}
-
 	record Batch(Execution execution, UUID id, Page page, List<OzgCloudVorgang> items) {
 	}
 
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
index 643bbeb..564df21 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
@@ -31,6 +31,7 @@ import java.util.stream.Stream;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
+import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationManagerConfiguration;
 import de.ozgcloud.aggregation.AggregationManagerProperties;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
@@ -63,8 +64,8 @@ class AggregationDataRemoteService {
 	}
 
 	SendAggregationDataResponseObserver buildSendAggregationDataResponseObserver(Aggregation aggregation) {
-		var requestData = new RequestData(properties.getMandant(), aggregation.aggregationName,
-				toGrpcAggregationDataStream(aggregation.documentEntries).iterator());
+		var requestData = new RequestData(properties.getMandant(), aggregation.aggregationName(),
+				toGrpcAggregationDataStream(aggregation.documentEntries()).iterator());
 		return new SendAggregationDataResponseObserver(configuration.getFetchingBatchSize(), requestData);
 	}
 
@@ -139,10 +140,6 @@ class AggregationDataRemoteService {
 		}
 	}
 
-	@Builder
-	public record Aggregation(String aggregationName, Stream<DocumentEntry> documentEntries) {
-	}
-
 	@Builder
 	record RequestData(String mandant, String aggregationName, Iterator<GrpcAggregationData> aggregationDataIterator) {
 	}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
new file mode 100644
index 0000000..92b7b99
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import java.util.concurrent.ExecutionException;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.aggregation.Aggregation;
+import de.ozgcloud.aggregation.AggregationDataLoader;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@Component
+@Scope("prototype")
+@RequiredArgsConstructor
+@Log4j2
+class AggregationRemoteDataLoader implements AggregationDataLoader {
+
+	private final AggregationDataRemoteService service;
+
+	@Override
+	public void loadIntoTarget(Aggregation aggregation) {
+		try {
+			service.sendAggregationData(aggregation).get();
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+			throw new TechnicalException("Waiting for sending aggregation data to complete was interrupted.", e);
+		} catch (ExecutionException e) {
+			throw new TechnicalException("Error on sending aggregation data.", e);
+		}
+	}
+}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepository.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepository.java
index 0a31216..a2abb1d 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepository.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepository.java
@@ -1,12 +1,12 @@
 package de.ozgcloud.aggregation.warehouse;
 
-import java.util.List;
+import java.util.stream.Stream;
 
 interface CustomWarehouseRepository {
 
 	DocumentEntry saveInCollection(DocumentEntry documentEntry, String collectionName);
 
-	List<DocumentEntry> saveAllInCollection(Iterable<DocumentEntry> documentEntries, String collectionName);
+	void saveAllInCollection(Stream<DocumentEntry> documentEntries, String collectionName);
 
 	void clearCollection(String collectionName);
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImpl.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImpl.java
index a5169d6..f2ce884 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImpl.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImpl.java
@@ -1,7 +1,6 @@
 package de.ozgcloud.aggregation.warehouse;
 
-import java.util.List;
-import java.util.stream.StreamSupport;
+import java.util.stream.Stream;
 
 import org.springframework.data.mongodb.core.MongoTemplate;
 
@@ -13,8 +12,8 @@ class CustomWarehouseRepositoryImpl implements CustomWarehouseRepository {
 	private final MongoTemplate mongoTemplate;
 
 	@Override
-	public List<DocumentEntry> saveAllInCollection(Iterable<DocumentEntry> documentEntries, String collectionName) {
-		return StreamSupport.stream(documentEntries.spliterator(), false).map(entry -> saveInCollection(entry, collectionName)).toList();
+	public void saveAllInCollection(Stream<DocumentEntry> documentEntries, String collectionName) {
+		documentEntries.forEach(entry -> saveInCollection(entry, collectionName));
 	}
 
 	@Override
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationTestFactory.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationTestFactory.java
similarity index 85%
rename from aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationTestFactory.java
rename to aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationTestFactory.java
index 6d167dd..4f99f08 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationTestFactory.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationTestFactory.java
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-package de.ozgcloud.aggregation.extern;
+package de.ozgcloud.aggregation;
 
 import java.util.stream.Stream;
 
@@ -35,12 +35,12 @@ public class AggregationTestFactory {
 	public static final String AGGREGATION_NAME = LoremIpsum.getInstance().getWords(1);
 	public static final DocumentEntry DOCUMENT_ENTRY = DocumentEntryTestFactory.create();
 
-	public static AggregationDataRemoteService.Aggregation create() {
+	public static Aggregation create() {
 		return createBuilder().build();
 	}
 
-	public static AggregationDataRemoteService.Aggregation.AggregationBuilder createBuilder() {
-		return AggregationDataRemoteService.Aggregation.builder()
+	public static Aggregation.AggregationBuilder createBuilder() {
+		return Aggregation.builder()
 				.aggregationName(AGGREGATION_NAME)
 				.documentEntries(Stream.of(DOCUMENT_ENTRY));
 	}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
new file mode 100644
index 0000000..5af0267
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import com.thedeanda.lorem.LoremIpsum;
+
+import de.ozgcloud.aggregation.warehouse.DocumentEntry;
+import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
+
+class AggregationWarehouseDataLoaderTest {
+
+	@Mock
+	private WarehouseRepository repository;
+	@Spy
+	@InjectMocks
+	private AggregationWarehouseDataLoader loader;
+
+	@Nested
+	class TestLoadIntoTarget {
+
+		private static final String COLLECTION_NAME = LoremIpsum.getInstance().getWords(1);
+		private final Aggregation aggregation = AggregationTestFactory.create();
+
+		@Captor
+		private ArgumentCaptor<Stream<DocumentEntry>> documentEntriesCaptor;
+
+		@BeforeEach
+		void init() {
+			doReturn(COLLECTION_NAME).when(loader).getCollectionName(aggregation);
+		}
+
+		@Test
+		void shouldGetCollectionName() {
+			loader.loadIntoTarget(aggregation);
+
+			verify(loader).getCollectionName(aggregation);
+		}
+
+		@Test
+		void shouldClearCollection() {
+			loader.loadIntoTarget(aggregation);
+
+			verify(repository).clearCollection(COLLECTION_NAME);
+		}
+
+		@Test
+		void shouldSaveDocumentEntriesInCollection() {
+			loader.loadIntoTarget(aggregation);
+
+			verify(repository).saveAllInCollection(documentEntriesCaptor.capture(), eq(COLLECTION_NAME));
+			assertThat(documentEntriesCaptor.getValue()).containsExactly(AggregationTestFactory.DOCUMENT_ENTRY);
+		}
+	}
+
+	@Nested
+	class TestGetCollectionName {
+
+		@Test
+		void shouldReturnAggregationName() {
+			var collectionName = loader.getCollectionName(AggregationTestFactory.create());
+
+			assertThat(collectionName).isEqualTo(AggregationTestFactory.AGGREGATION_NAME);
+		}
+
+		@ParameterizedTest
+		@NullAndEmptySource
+		void shouldReturnCollectionNameFromDocumentEntry(String aggregationName) {
+			var collectionName = loader.getCollectionName(AggregationTestFactory.createBuilder().aggregationName(aggregationName).build());
+
+			assertThat(collectionName).isEqualTo(DocumentEntry.COLLECTION);
+		}
+	}
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregatorTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregatorTest.java
index 2d66f74..684c5df 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregatorTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregatorTest.java
@@ -82,7 +82,7 @@ class AggregatorTest {
 
 			private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create();
 			private final FormIdentifier formIdentifier = AggregationMappingTestFactory.FORM_IDENTIFIER;
-			private final String collectionName = AggregationMappingTestFactory.NAME;
+			private final String aggregationName = AggregationMappingTestFactory.NAME;
 
 			@Test
 			void shouldReturnSelf() {
@@ -99,10 +99,10 @@ class AggregatorTest {
 			}
 
 			@Test
-			void shouldSetCollectionName() {
+			void shouldSetAggregationName() {
 				var result = aggregator.withAggregationMapping(aggregationMapping);
 
-				assertThat(ReflectionTestUtils.getField(result, "collectionName")).isEqualTo(collectionName);
+				assertThat(ReflectionTestUtils.getField(result, "aggregationName")).isEqualTo(aggregationName);
 			}
 		}
 
@@ -124,10 +124,10 @@ class AggregatorTest {
 			}
 
 			@Test
-			void shouldNotSetCollectionName() {
+			void shouldNotSetAggregationName() {
 				var result = aggregator.withAggregationMapping(null);
 
-				assertThat(ReflectionTestUtils.getField(result, "collectionName")).isEqualTo(DocumentEntry.COLLECTION);
+				assertThat(ReflectionTestUtils.getField(result, "aggregationName")).isNull();
 			}
 		}
 	}
@@ -152,6 +152,27 @@ class AggregatorTest {
 		}
 	}
 
+	@Nested
+	class TestWithLoader {
+
+		@Mock
+		private AggregationDataLoader loader;
+
+		@Test
+		void shouldReturnSelf() {
+			var result = aggregator.withLoader(loader);
+
+			assertThat(result).isSameAs(aggregator);
+		}
+
+		@Test
+		void shouldSetLoader() {
+			aggregator.withLoader(loader);
+
+			assertThat(ReflectionTestUtils.getField(aggregator, "loader")).isSameAs(loader);
+		}
+	}
+
 	@Nested
 	class TestAggregate {
 		@Mock
@@ -165,7 +186,7 @@ class AggregatorTest {
 		void setUp() {
 			doReturn(Stream.of(batchOfVorgaenge)).when(aggregator).extractBatchesOfVorgaengeFromDataSource();
 			doReturn(Stream.of(batchOfDeletedVorgaenge)).when(aggregator).extractBatchesOfDeletedVorgaengeFromDataSource();
-			doNothing().when(aggregator).loadVorgaengeIntoRepository(any());
+			doNothing().when(aggregator).loadVorgaenge(any());
 		}
 
 		@Test
@@ -186,7 +207,7 @@ class AggregatorTest {
 		void shouldLoadVorgaengeIntoRepository() {
 			aggregator.aggregate();
 
-			verify(aggregator).loadVorgaengeIntoRepository(batchStreamCaptor.capture());
+			verify(aggregator).loadVorgaenge(batchStreamCaptor.capture());
 			assertThat(batchStreamCaptor.getValue()).containsExactly(batchOfVorgaenge, batchOfDeletedVorgaenge);
 		}
 	}
@@ -485,67 +506,55 @@ class AggregatorTest {
 	}
 
 	@Nested
-	class TestLoadVorgaengeIntoRepository {
+	class TestLoadVorgaenge {
 
 		@Mock
 		private Execution execution;
 		@Mock
 		private Batch batch;
+		@Mock
+		private AggregationDataLoader loader;
 		private final DocumentEntry documentEntry = DocumentEntryTestFactory.create();
 		private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create();
 		@Captor
-		private ArgumentCaptor<List<DocumentEntry>> documentEntriesCaptor;
+		private ArgumentCaptor<Aggregation> aggregationCaptor;
 
 		@BeforeEach
 		void init() {
-			aggregator = aggregator.withAggregationMapping(aggregationMapping);
-			doReturn(List.of(documentEntry)).when(aggregator).transformBatchToDocumentEntries(any());
-			doNothing().when(aggregator).loadDocumentEntriesIntoRepository(any());
+			aggregator = aggregator.withAggregationMapping(aggregationMapping).withLoader(loader);
 		}
 
 		@Test
-		void shouldDropCollection() {
-			loadVorgaengeIntoRepository();
+		void shouldLoadIntoTarget() {
+			loadVorgaenge();
 
-			verify(repository).clearCollection(AggregationMappingTestFactory.NAME);
+			verify(loader).loadIntoTarget(any(Aggregation.class));
 		}
 
 		@Test
 		void shouldTransform() {
-			loadVorgaengeIntoRepository();
+			doReturn(Stream.of(documentEntry)).when(aggregator).transformBatchToDocumentEntries(any());
 
+			loadVorgaenge();
+
+			getArgumentOfLoadIntoTarget().documentEntries().toList();
 			verify(aggregator).transformBatchToDocumentEntries(batch);
 		}
 
 		@Test
-		void shouldLoadIntoRepository() {
-			loadVorgaengeIntoRepository();
+		void shouldSetAggregationName() {
+			loadVorgaenge();
 
-			verify(aggregator).loadDocumentEntriesIntoRepository(documentEntriesCaptor.capture());
-			assertThat(documentEntriesCaptor.getValue()).containsExactly(documentEntry);
+			assertThat(getArgumentOfLoadIntoTarget().aggregationName()).isEqualTo(AggregationMappingTestFactory.NAME);
 		}
 
-		private void loadVorgaengeIntoRepository() {
-			aggregator.loadVorgaengeIntoRepository(Stream.of(batch));
-		}
-	}
-
-	@Nested
-	class TestLoadDocumentEntriesIntoRepository {
-
-		private final List<DocumentEntry> documentEntries = List.of(DocumentEntryTestFactory.create());
-		private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create();
-
-		@BeforeEach
-		void init() {
-			aggregator = aggregator.withAggregationMapping(aggregationMapping);
+		private Aggregation getArgumentOfLoadIntoTarget() {
+			verify(loader).loadIntoTarget(aggregationCaptor.capture());
+			return aggregationCaptor.getValue();
 		}
 
-		@Test
-		void shouldSaveDocumentEntriesInCollection() {
-			aggregator.loadDocumentEntriesIntoRepository(documentEntries);
-
-			verify(repository).saveAllInCollection(documentEntries, AggregationMappingTestFactory.NAME);
+		private void loadVorgaenge() {
+			aggregator.loadVorgaenge(Stream.of(batch));
 		}
 	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
index 2023e0c..c3711cc 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
@@ -45,8 +45,10 @@ import org.mockito.Spy;
 
 import com.thedeanda.lorem.LoremIpsum;
 
+import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationManagerConfiguration;
 import de.ozgcloud.aggregation.AggregationManagerProperties;
+import de.ozgcloud.aggregation.AggregationTestFactory;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
 import de.ozgcloud.aggregation.data.GrpcAggregationData;
 import de.ozgcloud.aggregation.data.GrpcSendAggregationDataRequest;
@@ -73,7 +75,7 @@ class AggregationDataRemoteServiceTest {
 	@Nested
 	class TestSendAggregationData {
 
-		private final AggregationDataRemoteService.Aggregation aggregation = AggregationTestFactory.create();
+		private final Aggregation aggregation = AggregationTestFactory.create();
 
 		@Mock
 		private SendAggregationDataResponseObserver responseObserver;
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
new file mode 100644
index 0000000..e6db338
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+
+import de.ozgcloud.aggregation.Aggregation;
+import de.ozgcloud.aggregation.AggregationTestFactory;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import lombok.SneakyThrows;
+
+class AggregationRemoteDataLoaderTest {
+
+	@Mock
+	private AggregationDataRemoteService remoteService;
+	@Spy
+	@InjectMocks
+	private AggregationRemoteDataLoader loader;
+
+	@Nested
+	class TestLoadIntoTarget {
+
+		private final Aggregation aggregation = AggregationTestFactory.create();
+		@Mock
+		private Future<Void> future;
+
+		@BeforeEach
+		void init() {
+			when(remoteService.sendAggregationData(any())).thenReturn(future);
+		}
+
+		@Test
+		@SneakyThrows
+		void shouldSendAggregationData() {
+			loader.loadIntoTarget(aggregation);
+
+			verify(remoteService).sendAggregationData(aggregation);
+		}
+
+		@Test
+		@SneakyThrows
+		void shouldGetFromFuture() {
+			loader.loadIntoTarget(aggregation);
+
+			verify(future).get();
+		}
+
+		@Nested
+		class TestOnInterruptedException {
+
+			private final InterruptedException exception = new InterruptedException();
+
+			@BeforeEach
+			@SneakyThrows
+			void mock() {
+				when(future.get()).thenThrow(exception);
+			}
+
+			@Test
+			void shouldThrowTechnicalException() {
+				assertThrows(TechnicalException.class, () -> loader.loadIntoTarget(aggregation));
+			}
+
+			@Test
+			void shouldInterruptThread() {
+				try {
+					loader.loadIntoTarget(aggregation);
+				} catch (TechnicalException e) {
+					// expected
+				}
+
+				assertThat(Thread.currentThread().isInterrupted()).isTrue();
+			}
+		}
+
+		@Nested
+		class TestOnExecutionException {
+
+			private final ExecutionException exception = new ExecutionException(new Exception());
+
+			@BeforeEach
+			@SneakyThrows
+			void mock() {
+				when(future.get()).thenThrow(exception);
+			}
+
+			@Test
+			void shouldThrowTechnicalException() {
+				assertThrows(TechnicalException.class, () -> loader.loadIntoTarget(aggregation));
+			}
+		}
+	}
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImplTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImplTest.java
index 194a372..171257c 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImplTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/CustomWarehouseRepositoryImplTest.java
@@ -1,7 +1,6 @@
 package de.ozgcloud.aggregation.warehouse;
 
 import static org.assertj.core.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
 import java.util.List;
@@ -33,20 +32,10 @@ class CustomWarehouseRepositoryImplTest {
 
 		@Test
 		void shouldCallSaveInCollection() {
-			repository.saveAllInCollection(documentEntries, collectionName);
+			repository.saveAllInCollection(documentEntries.stream(), collectionName);
 
 			verify(repository).saveInCollection(documentEntry, collectionName);
 		}
-
-		@Test
-		void shouldReturnSavedDocumentEntry() {
-			var savedDocumentEntry = DocumentEntryTestFactory.create();
-			doReturn(savedDocumentEntry).when(repository).saveInCollection(any(), any());
-
-			var returnedDocumentEntries = repository.saveAllInCollection(documentEntries, collectionName);
-
-			assertThat(returnedDocumentEntries).containsExactly(savedDocumentEntry);
-		}
 	}
 
 	@Nested
-- 
GitLab


From 1863b99a21582959dce003a99e5dfb63cb39c017 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Fri, 25 Apr 2025 18:32:06 +0200
Subject: [PATCH 07/14] OZG-7811 OZG-8099 runWithDefaultTransformation pro
 scope

---
 .../aggregation/AggregationManagerRunner.java |  31 +--
 .../aggregation/TransformationProperties.java |   6 +-
 .../AggregationManagerRunnerTest.java         | 199 +++++++++++-------
 .../AggregationMappingTestFactory.java        |   4 +-
 4 files changed, 147 insertions(+), 93 deletions(-)

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
index 3d5596e..f3b4730 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
@@ -23,8 +23,9 @@
  */
 package de.ozgcloud.aggregation;
 
-import java.util.Objects;
+import java.util.Arrays;
 import java.util.UUID;
+import java.util.stream.Stream;
 
 import org.apache.logging.log4j.ThreadContext;
 import org.springframework.beans.factory.annotation.Lookup;
@@ -54,19 +55,25 @@ public abstract class AggregationManagerRunner implements CommandLineRunner {
 
 	@Override
 	public void run(String... args) throws TransformationException {
-		var identifier = transformationProperties.getIdentifier();
-		var aggregationMappings = transformationProperties.getAggregationMappings();
-		if (Objects.isNull(aggregationMappings) || aggregationMappings.isEmpty()) {
-			runWithDefaultTransformation(transformationService.load(identifier, null));
-		} else {
-			aggregationMappings.stream()
-					.forEach(aggregationMapping -> runWithTransformation(transformationService.load(identifier, aggregationMapping),
-							aggregationMapping));
-		}
+		getScopesWithoutConfiguredTransformations().forEach(this::runWithDefaultTransformation);
+		runWithConfiguredTransformations();
+	}
+
+	Stream<AggregationMapping.Scope> getScopesWithoutConfiguredTransformations() {
+		return Arrays.stream(AggregationMapping.Scope.values()).filter(this::hasNoConfiguredTransformationsWithScope);
+	}
+
+	boolean hasNoConfiguredTransformationsWithScope(AggregationMapping.Scope scope) {
+		return transformationProperties.getAggregationMappings().stream().map(AggregationMapping::getScope).noneMatch(scope::equals);
+	}
+
+	void runWithDefaultTransformation(AggregationMapping.Scope scope) throws TransformationException {
+		runWithTransformation(transformationService.load(transformationProperties.getIdentifier(), null), null);
 	}
 
-	void runWithDefaultTransformation(Transformation transformation) {
-		runWithTransformation(transformation, null);
+	void runWithConfiguredTransformations() {
+		transformationProperties.getAggregationMappings().forEach(aggregationMapping ->
+				runWithTransformation(transformationService.load(transformationProperties.getIdentifier(), aggregationMapping), aggregationMapping));
 	}
 
 	void runWithTransformation(Transformation transformation, AggregationMapping aggregationMapping) {
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java
index aadc1ca..25c70ca 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/TransformationProperties.java
@@ -23,15 +23,15 @@
  */
 package de.ozgcloud.aggregation;
 
+import java.util.ArrayList;
 import java.util.List;
 
-import jakarta.validation.Valid;
-
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.validation.annotation.Validated;
 
 import de.ozgcloud.aggregation.transformation.AggregationMapping;
+import jakarta.validation.Valid;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.log4j.Log4j2;
@@ -49,7 +49,7 @@ public class TransformationProperties {
 	 * field mappings should be applied
 	 */
 	@Valid
-	private List<AggregationMapping> aggregationMappings;
+	private List<AggregationMapping> aggregationMappings = new ArrayList<>();
 
 	/*
 	 * mapping definition for the entry id
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
index ef2a1f2..b4c365f 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
@@ -27,13 +27,16 @@ import static org.assertj.core.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.*;
 import static org.mockito.Mockito.*;
 
-import java.util.Collections;
 import java.util.List;
+import java.util.stream.Stream;
 
 import org.apache.commons.lang3.RandomUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentMatcher;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -72,129 +75,171 @@ class AggregationManagerRunnerTest {
 
 	@Nested
 	class TestRun {
-		private final String identifier = LoremIpsum.getInstance().getWords(1);
 
 		@BeforeEach
-		void mock() {
-			when(transformationProperties.getIdentifier()).thenReturn(identifier);
-			doNothing().when(runner).runWithTransformation(any(), any());
+		void init() {
+			doNothing().when(runner).runWithDefaultTransformation(any());
+			doNothing().when(runner).runWithConfiguredTransformations();
 		}
 
 		@Test
-		void shouldGetAggregationMappings() {
+		void shouldGetScopesWithoutConfiguredTransformations() {
 			runner.run();
 
-			verify(transformationProperties).getAggregationMappings();
+			verify(runner).getScopesWithoutConfiguredTransformations();
 		}
 
-		@Test
-		void shouldGetIdentifier() {
+		@ParameterizedTest
+		@EnumSource
+		void shouldRunWithDefaultTransformationForScopesWithoutConfiguredOnes(AggregationMapping.Scope scope) {
+			doReturn(Stream.of(scope)).when(runner).getScopesWithoutConfiguredTransformations();
+
 			runner.run();
 
-			verify(transformationProperties).getIdentifier();
+			verify(runner).runWithDefaultTransformation(scope);
 		}
 
-		@Nested
-		class TestOnAggregationMappingConfigured {
-			private final List<AggregationMapping> aggregationMappings = List.of(AggregationMappingTestFactory.create(),
-					AggregationMappingTestFactory.create());
-
-			@Mock
-			private Transformation firstTransformation;
-			@Mock
-			private Transformation secondTransformation;
-
-			@BeforeEach
-			void mock() {
-				when(transformationProperties.getAggregationMappings()).thenReturn(aggregationMappings);
-				when(transformationService.load(any(), any())).thenReturn(firstTransformation, secondTransformation);
-			}
+		@Test
+		void shouldRunWithConfiguredTransformations() {
+			runner.run();
 
-			@Test
-			void shouldLoadTransformationForEachAggregationMapping() {
-				runner.run();
+			verify(runner).runWithConfiguredTransformations();
+		}
+	}
 
-				aggregationMappings.forEach(mapping -> verify(transformationService).load(identifier, mapping));
-			}
+	@Nested
+	class TestGetScopesWithoutConfiguredTransformations {
 
-			@Test
-			void shouldRunWithTransformationForEachTransformation() {
-				runner.run();
+		@ParameterizedTest
+		@EnumSource
+		void shouldCheckForConfiguredTransformationsWithScope(AggregationMapping.Scope scope) {
+			runner.getScopesWithoutConfiguredTransformations().toList();
 
-				verify(runner).runWithTransformation(firstTransformation, aggregationMappings.get(0));
-				verify(runner).runWithTransformation(secondTransformation, aggregationMappings.get(1));
-			}
+			verify(runner).hasNoConfiguredTransformationsWithScope(scope);
 		}
 
-		@Nested
-		class TestOnAggregationMappingsNull {
-			@Mock
-			private Transformation transformation;
+		@ParameterizedTest
+		@EnumSource
+		void shouldReturnScopes(AggregationMapping.Scope scope) {
+			doReturn(true).when(runner).hasNoConfiguredTransformationsWithScope(scope);
+			doReturn(false).when(runner).hasNoConfiguredTransformationsWithScope(AdditionalMatchers.not(eq(scope)));
 
-			@BeforeEach
-			void mock() {
-				when(transformationProperties.getAggregationMappings()).thenReturn(null);
-				when(transformationService.load(any(), any())).thenReturn(transformation);
-			}
+			var scopes = runner.getScopesWithoutConfiguredTransformations();
 
-			@Test
-			void shouldLoadTransformation() {
-				runner.run();
+			assertThat(scopes).containsExactly(scope);
+		}
+	}
 
-				verify(transformationService).load(identifier, null);
-			}
+	@Nested
+	class TestHasNoConfiguredTransformationsWithScope {
 
-			@Test
-			void shouldCallRunWithDefaultTransformation() {
-				runner.run();
+		@Test
+		void shouldGetAggregationMappings() {
+			runner.hasNoConfiguredTransformationsWithScope(AggregationMapping.Scope.INTERN);
 
-				verify(runner).runWithDefaultTransformation(transformation);
-			}
+			verify(transformationProperties).getAggregationMappings();
 		}
 
-		@Nested
-		class TestOnAggregationMappingsEmpty {
-			@Mock
-			private Transformation transformation;
+		@Test
+		void shouldReturnFalseIfThereAreConfiguredTransformations() {
+			when(transformationProperties.getAggregationMappings()).thenReturn(List.of(AggregationMappingTestFactory.create()));
 
-			@BeforeEach
-			void mock() {
-				when(transformationProperties.getAggregationMappings()).thenReturn(Collections.emptyList());
-				when(transformationService.load(any(), any())).thenReturn(transformation);
-			}
+			var hasNoTransformations = runner.hasNoConfiguredTransformationsWithScope(AggregationMappingTestFactory.SCOPE);
 
-			@Test
-			void shouldLoadTransformation() {
-				runner.run();
+			assertThat(hasNoTransformations).isFalse();
+		}
 
-				verify(transformationService).load(identifier, null);
-			}
+		@Test
+		void shouldReturnTrueIfThereAreNoConfiguredTransformations() {
+			when(transformationProperties.getAggregationMappings()).thenReturn(List.of(AggregationMappingTestFactory.createBuilder().scope(
+					AggregationMapping.Scope.INTERN).build()));
 
-			@Test
-			void shouldCallRunWithDefaultTransformation() {
-				runner.run();
+			var hasNoTransformations = runner.hasNoConfiguredTransformationsWithScope(AggregationMapping.Scope.EXTERN);
 
-				verify(runner).runWithDefaultTransformation(transformation);
-			}
+			assertThat(hasNoTransformations).isTrue();
 		}
 	}
 
 	@Nested
 	class TestRunWithDefaultTransformation {
 
+		private final String identifier = LoremIpsum.getInstance().getWords(1);
+
 		@Mock
 		private Transformation transformation;
 
-		@Test
-		void shouldCallRunWithTransformation() {
+		@BeforeEach
+		void init() {
+			when(transformationProperties.getIdentifier()).thenReturn(identifier);
+			when(transformationService.load(any(), any())).thenReturn(transformation);
 			doNothing().when(runner).runWithTransformation(any(), any());
+		}
+
+		@Test
+		void shouldGetIdentifier() {
+			runner.runWithDefaultTransformation(AggregationMapping.Scope.INTERN);
 
-			runner.runWithDefaultTransformation(transformation);
+			verify(transformationProperties).getIdentifier();
+		}
+
+		@Test
+		void shouldLoadTransformation() {
+			runner.runWithDefaultTransformation(AggregationMapping.Scope.INTERN);
+
+			verify(transformationService).load(identifier, null);
+		}
+
+		@Test
+		void shouldRunWithTransformation() {
+			runner.runWithDefaultTransformation(AggregationMapping.Scope.INTERN);
 
 			verify(runner).runWithTransformation(eq(transformation), isNull());
 		}
 	}
 
+	@Nested
+	class TestRunWithConfiguredTransformations {
+
+		private final String identifier = LoremIpsum.getInstance().getWords(1);
+		private final List<AggregationMapping> aggregationMappings = List.of(AggregationMappingTestFactory.create(),
+				AggregationMappingTestFactory.create());
+
+		@Mock
+		private Transformation firstTransformation;
+		@Mock
+		private Transformation secondTransformation;
+
+		@BeforeEach
+		void mock() {
+			when(transformationProperties.getAggregationMappings()).thenReturn(aggregationMappings);
+			when(transformationProperties.getIdentifier()).thenReturn(identifier);
+			when(transformationService.load(any(), any())).thenReturn(firstTransformation, secondTransformation);
+			doNothing().when(runner).runWithTransformation(any(), any());
+		}
+
+		@Test
+		void shouldGetIdentifier() {
+			runner.runWithConfiguredTransformations();
+
+			verify(transformationProperties, times(2)).getIdentifier();
+		}
+
+		@Test
+		void shouldLoadTransformationForEachAggregationMapping() {
+			runner.runWithConfiguredTransformations();
+
+			aggregationMappings.forEach(mapping -> verify(transformationService).load(identifier, mapping));
+		}
+
+		@Test
+		void shouldRunWithTransformationForEachTransformation() {
+			runner.runWithConfiguredTransformations();
+
+			verify(runner).runWithTransformation(firstTransformation, aggregationMappings.get(0));
+			verify(runner).runWithTransformation(secondTransformation, aggregationMappings.get(1));
+		}
+	}
+
 	@Nested
 	class TestRunWithTransformation {
 
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java
index 44a5c25..8986c79 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/transformation/AggregationMappingTestFactory.java
@@ -34,6 +34,7 @@ public class AggregationMappingTestFactory {
 	public static final FormIdentifier FORM_IDENTIFIER = FormIdentifierTestFactory.create();
 	public static final FieldMapping MAPPING = FieldMappingTestFactory.create();
 	public static final String NAME = LoremIpsum.getInstance().getWords(1);
+	public static final AggregationMapping.Scope SCOPE = AggregationMapping.Scope.INTERN;
 
 	public static AggregationMapping create() {
 		return createBuilder().build();
@@ -43,6 +44,7 @@ public class AggregationMappingTestFactory {
 		return AggregationMapping.builder()
 				.formIdentifier(FORM_IDENTIFIER)
 				.fieldMapping(MAPPING)
-				.name(NAME);
+				.name(NAME)
+				.scope(SCOPE);
 	}
 }
-- 
GitLab


From 2ae5aca28fcdf7d87bb355be34213268961f9bdc Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Mon, 28 Apr 2025 15:01:55 +0200
Subject: [PATCH 08/14] OZG-7811 OZG-8099 Pass Loader to Aggregator

---
 .../aggregation/AggregationDataLoader.java    |   4 +
 .../AggregationDataLoaderRegistry.java        |  63 +++++++
 .../aggregation/AggregationManagerRunner.java |  25 +--
 .../AggregationWarehouseDataLoader.java       |  10 +-
 .../extern/AggregationRemoteDataLoader.java   |  11 +-
 .../AggregationDataLoaderRegistryTest.java    | 126 ++++++++++++++
 .../AggregationManagerRunnerTest.java         | 158 ++++++++++++------
 .../AggregationWarehouseDataLoaderTest.java   |  21 +++
 .../AggregationRemoteDataLoaderTest.java      |  22 +++
 9 files changed, 369 insertions(+), 71 deletions(-)
 create mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
index 0bd3bd6..f116b96 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
@@ -23,7 +23,11 @@
  */
 package de.ozgcloud.aggregation;
 
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
+
 public interface AggregationDataLoader {
 
 	void loadIntoTarget(Aggregation aggregation);
+
+	boolean supportsScope(AggregationMapping.Scope scope);
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
new file mode 100644
index 0000000..363333b
--- /dev/null
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.stereotype.Component;
+
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import lombok.RequiredArgsConstructor;
+
+@Component
+@RequiredArgsConstructor
+class AggregationDataLoaderRegistry {
+
+	private final List<AggregationDataLoader> loaders;
+	private final Map<AggregationMapping.Scope, AggregationDataLoader> scopeToLoader = new HashMap<>();
+
+	@PostConstruct
+	void buildLoadersMap() {
+
+	}
+
+	public boolean hasLoader(AggregationMapping.Scope scope) {
+		return loaders.stream().anyMatch(supportsScope(scope));
+	}
+
+	public AggregationDataLoader getLoader(AggregationMapping.Scope scope) {
+		return loaders.stream().filter(supportsScope(scope)).findFirst()
+				.orElseThrow(() -> new TechnicalException("No loader found for scope " + scope));
+	}
+
+	private Predicate<AggregationDataLoader> supportsScope(AggregationMapping.Scope scope) {
+		return loader -> loader.supportsScope(scope);
+	}
+}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
index f3b4730..9debd13 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
@@ -52,6 +52,7 @@ public abstract class AggregationManagerRunner implements CommandLineRunner {
 	private final AggregationManagerConfiguration config;
 	private final TransformationProperties transformationProperties;
 	private final TransformationService transformationService;
+	private final AggregationDataLoaderRegistry loaderRegistry;
 
 	@Override
 	public void run(String... args) throws TransformationException {
@@ -60,36 +61,38 @@ public abstract class AggregationManagerRunner implements CommandLineRunner {
 	}
 
 	Stream<AggregationMapping.Scope> getScopesWithoutConfiguredTransformations() {
-		return Arrays.stream(AggregationMapping.Scope.values()).filter(this::hasNoConfiguredTransformationsWithScope);
+		return Arrays.stream(AggregationMapping.Scope.values()).filter(loaderRegistry::hasLoader).filter(this::hasNoConfiguredTransformations);
 	}
 
-	boolean hasNoConfiguredTransformationsWithScope(AggregationMapping.Scope scope) {
+	boolean hasNoConfiguredTransformations(AggregationMapping.Scope scope) {
 		return transformationProperties.getAggregationMappings().stream().map(AggregationMapping::getScope).noneMatch(scope::equals);
 	}
 
 	void runWithDefaultTransformation(AggregationMapping.Scope scope) throws TransformationException {
-		runWithTransformation(transformationService.load(transformationProperties.getIdentifier(), null), null);
+		runWithTransformation(
+				transformationService.load(transformationProperties.getIdentifier(), null),
+				createAggregator(scope));
 	}
 
 	void runWithConfiguredTransformations() {
-		transformationProperties.getAggregationMappings().forEach(aggregationMapping ->
-				runWithTransformation(transformationService.load(transformationProperties.getIdentifier(), aggregationMapping), aggregationMapping));
+		transformationProperties.getAggregationMappings().forEach(aggregationMapping -> runWithTransformation(
+				transformationService.load(transformationProperties.getIdentifier(), aggregationMapping),
+				createAggregator(aggregationMapping.getScope()).withAggregationMapping(aggregationMapping)));
 	}
 
-	void runWithTransformation(Transformation transformation, AggregationMapping aggregationMapping) {
+	void runWithTransformation(Transformation transformation, Aggregator aggregator) {
 		try (Execution execution = new Execution(transformation)) {
 			ThreadContext.put(MDC_EXECUTION, execution.id.toString());
-			prepareAggregator(execution, aggregationMapping).aggregate();
+			aggregator.withExecution(execution).aggregate();
 		} finally {
 			ThreadContext.remove(MDC_EXECUTION);
 		}
 	}
 
-	Aggregator prepareAggregator(Execution execution, AggregationMapping aggregationMapping) {
+	Aggregator createAggregator(AggregationMapping.Scope scope) {
 		return createAggregator()
-				.withExecution(execution)
-				.withAggregationMapping(aggregationMapping)
-				.withBatchSize(config.getFetchingBatchSize());
+				.withBatchSize(config.getFetchingBatchSize())
+				.withLoader(loaderRegistry.getLoader(scope));
 	}
 
 	@Lookup
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
index 1873800..5694975 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
@@ -24,19 +24,18 @@
 package de.ozgcloud.aggregation;
 
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.aggregation.warehouse.DocumentEntry;
 import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
 @Component
-@Scope("prototype")
 @RequiredArgsConstructor
 @Log4j2
-class AggregationWarehouseDataLoader implements AggregationDataLoader {
+public class AggregationWarehouseDataLoader implements AggregationDataLoader {
 
 	private final WarehouseRepository repository;
 
@@ -50,4 +49,9 @@ class AggregationWarehouseDataLoader implements AggregationDataLoader {
 	String getCollectionName(Aggregation aggregation) {
 		return StringUtils.isNotBlank(aggregation.aggregationName()) ? aggregation.aggregationName() : DocumentEntry.COLLECTION;
 	}
+
+	@Override
+	public boolean supportsScope(AggregationMapping.Scope scope) {
+		return AggregationMapping.Scope.INTERN.equals(scope);
+	}
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
index 92b7b99..29ed0db 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
@@ -25,20 +25,21 @@ package de.ozgcloud.aggregation.extern;
 
 import java.util.concurrent.ExecutionException;
 
-import org.springframework.context.annotation.Scope;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Component;
 
 import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationDataLoader;
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
 @Component
-@Scope("prototype")
+@ConditionalOnProperty("grpc.client.aggregation-manager.address")
 @RequiredArgsConstructor
 @Log4j2
-class AggregationRemoteDataLoader implements AggregationDataLoader {
+public class AggregationRemoteDataLoader implements AggregationDataLoader {
 
 	private final AggregationDataRemoteService service;
 
@@ -53,4 +54,8 @@ class AggregationRemoteDataLoader implements AggregationDataLoader {
 			throw new TechnicalException("Error on sending aggregation data.", e);
 		}
 	}
+
+	public boolean supportsScope(AggregationMapping.Scope scope) {
+		return AggregationMapping.Scope.EXTERN.equals(scope);
+	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
new file mode 100644
index 0000000..cd7127d
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
+import de.ozgcloud.common.errorhandling.TechnicalException;
+import de.ozgcloud.common.test.ReflectionTestUtils;
+
+class AggregationDataLoaderRegistryTest {
+
+	@Mock
+	private AggregationDataLoader loader;
+	private AggregationDataLoaderRegistry registry;
+
+	@BeforeEach
+	void init() {
+		registry = spy(new AggregationDataLoaderRegistry(List.of(loader)));
+	}
+
+	@Nested
+	class TestBuildLoadersMap {
+
+		@Test
+		void shouldAddLoader() {
+		}
+
+		@SuppressWarnings("unchecked")
+		private Map<AggregationMapping.Scope, AggregationDataLoader> getScopeToLoader() {
+			return ReflectionTestUtils.getField(registry, "scopeToLoader", Map.class);
+		}
+	}
+
+	@Nested
+	class TestHasLoader {
+
+		@Test
+		void shouldCheckIfLoaderSupportsScope() {
+			var scope = AggregationMapping.Scope.INTERN;
+
+			registry.hasLoader(scope);
+
+			verify(loader).supportsScope(scope);
+		}
+
+		@Test
+		void shouldReturnFalseIfNoLoaderRegisteredForScope() {
+			when(loader.supportsScope(any())).thenReturn(false);
+
+			var hasLoader = registry.hasLoader(AggregationMapping.Scope.INTERN);
+
+			assertThat(hasLoader).isFalse();
+		}
+
+		@Test
+		void shouldReturnTrueIfLoaderRegisteredForScope() {
+			when(loader.supportsScope(any())).thenReturn(true);
+
+			var hasLoader = registry.hasLoader(AggregationMapping.Scope.INTERN);
+
+			assertThat(hasLoader).isTrue();
+		}
+	}
+
+	@Nested
+	class TestGetLoader {
+
+		private final AggregationMapping.Scope scope = AggregationMapping.Scope.INTERN;
+
+		@Test
+		void shouldCheckIfLoaderSupportsScope() {
+			when(loader.supportsScope(any())).thenReturn(true);
+
+			registry.getLoader(scope);
+
+			verify(loader).supportsScope(scope);
+		}
+
+		@Test
+		void shouldReturnLoaderThatSupportsScope() {
+			when(loader.supportsScope(any())).thenReturn(true);
+
+			var loaderSupportingScope = registry.getLoader(scope);
+
+			assertThat(loaderSupportingScope).isSameAs(loader);
+		}
+
+		@Test
+		void shouldThrowExceptionIfNoLoaderRegisteredForScope() {
+			when(loader.supportsScope(any())).thenReturn(false);
+
+			assertThatThrownBy(() -> registry.getLoader(scope)).isInstanceOf(TechnicalException.class);
+		}
+	}
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
index b4c365f..a494e09 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
@@ -49,14 +49,9 @@ import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.aggregation.transformation.AggregationMappingTestFactory;
 import de.ozgcloud.aggregation.transformation.Transformation;
 import de.ozgcloud.aggregation.transformation.TransformationService;
-import de.ozgcloud.aggregation.transformation.VorgangMapper;
-import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
-import de.ozgcloud.apilib.vorgang.OzgCloudVorgangService;
 
 class AggregationManagerRunnerTest {
 
-	@Mock
-	private OzgCloudVorgangService vorgangService;
 	@Mock
 	private AggregationManagerConfiguration config;
 	@Mock
@@ -64,9 +59,7 @@ class AggregationManagerRunnerTest {
 	@Mock
 	private TransformationService transformationService;
 	@Mock
-	private WarehouseRepository repository;
-	@Mock
-	private VorgangMapper vorgangMapper;
+	private AggregationDataLoaderRegistry loaderRegistry;
 	@Mock
 	private static Aggregator aggregator;
 	@Spy
@@ -78,7 +71,6 @@ class AggregationManagerRunnerTest {
 
 		@BeforeEach
 		void init() {
-			doNothing().when(runner).runWithDefaultTransformation(any());
 			doNothing().when(runner).runWithConfiguredTransformations();
 		}
 
@@ -93,6 +85,7 @@ class AggregationManagerRunnerTest {
 		@EnumSource
 		void shouldRunWithDefaultTransformationForScopesWithoutConfiguredOnes(AggregationMapping.Scope scope) {
 			doReturn(Stream.of(scope)).when(runner).getScopesWithoutConfiguredTransformations();
+			doNothing().when(runner).runWithDefaultTransformation(any());
 
 			runner.run();
 
@@ -110,32 +103,53 @@ class AggregationManagerRunnerTest {
 	@Nested
 	class TestGetScopesWithoutConfiguredTransformations {
 
+		@ParameterizedTest
+		@EnumSource
+		void shouldCheckForLoader(AggregationMapping.Scope scope) {
+			runner.getScopesWithoutConfiguredTransformations().toList();
+
+			verify(loaderRegistry).hasLoader(scope);
+		}
+
 		@ParameterizedTest
 		@EnumSource
 		void shouldCheckForConfiguredTransformationsWithScope(AggregationMapping.Scope scope) {
+			when(loaderRegistry.hasLoader(any())).thenReturn(true);
+
 			runner.getScopesWithoutConfiguredTransformations().toList();
 
-			verify(runner).hasNoConfiguredTransformationsWithScope(scope);
+			verify(runner).hasNoConfiguredTransformations(scope);
 		}
 
 		@ParameterizedTest
 		@EnumSource
-		void shouldReturnScopes(AggregationMapping.Scope scope) {
-			doReturn(true).when(runner).hasNoConfiguredTransformationsWithScope(scope);
-			doReturn(false).when(runner).hasNoConfiguredTransformationsWithScope(AdditionalMatchers.not(eq(scope)));
+		void shouldReturnScopesWithoutTransformations(AggregationMapping.Scope scope) {
+			doReturn(true).when(runner).hasNoConfiguredTransformations(scope);
+			doReturn(false).when(runner).hasNoConfiguredTransformations(AdditionalMatchers.not(eq(scope)));
+			when(loaderRegistry.hasLoader(any())).thenReturn(true);
 
 			var scopes = runner.getScopesWithoutConfiguredTransformations();
 
 			assertThat(scopes).containsExactly(scope);
 		}
+
+		@Test
+		void shouldOmitScopesWithoutLoaders() {
+			lenient().doReturn(true).when(runner).hasNoConfiguredTransformations(any());
+			when(loaderRegistry.hasLoader(any())).thenReturn(false);
+
+			var scopes = runner.getScopesWithoutConfiguredTransformations();
+
+			assertThat(scopes).isEmpty();
+		}
 	}
 
 	@Nested
-	class TestHasNoConfiguredTransformationsWithScope {
+	class TestHasNoConfiguredTransformations {
 
 		@Test
 		void shouldGetAggregationMappings() {
-			runner.hasNoConfiguredTransformationsWithScope(AggregationMapping.Scope.INTERN);
+			runner.hasNoConfiguredTransformations(AggregationMapping.Scope.INTERN);
 
 			verify(transformationProperties).getAggregationMappings();
 		}
@@ -144,7 +158,7 @@ class AggregationManagerRunnerTest {
 		void shouldReturnFalseIfThereAreConfiguredTransformations() {
 			when(transformationProperties.getAggregationMappings()).thenReturn(List.of(AggregationMappingTestFactory.create()));
 
-			var hasNoTransformations = runner.hasNoConfiguredTransformationsWithScope(AggregationMappingTestFactory.SCOPE);
+			var hasNoTransformations = runner.hasNoConfiguredTransformations(AggregationMappingTestFactory.SCOPE);
 
 			assertThat(hasNoTransformations).isFalse();
 		}
@@ -154,7 +168,7 @@ class AggregationManagerRunnerTest {
 			when(transformationProperties.getAggregationMappings()).thenReturn(List.of(AggregationMappingTestFactory.createBuilder().scope(
 					AggregationMapping.Scope.INTERN).build()));
 
-			var hasNoTransformations = runner.hasNoConfiguredTransformationsWithScope(AggregationMapping.Scope.EXTERN);
+			var hasNoTransformations = runner.hasNoConfiguredTransformations(AggregationMapping.Scope.EXTERN);
 
 			assertThat(hasNoTransformations).isTrue();
 		}
@@ -163,6 +177,7 @@ class AggregationManagerRunnerTest {
 	@Nested
 	class TestRunWithDefaultTransformation {
 
+		private final AggregationMapping.Scope scope = AggregationMapping.Scope.INTERN;
 		private final String identifier = LoremIpsum.getInstance().getWords(1);
 
 		@Mock
@@ -172,42 +187,58 @@ class AggregationManagerRunnerTest {
 		void init() {
 			when(transformationProperties.getIdentifier()).thenReturn(identifier);
 			when(transformationService.load(any(), any())).thenReturn(transformation);
+			doReturn(aggregator).when(runner).createAggregator(any());
 			doNothing().when(runner).runWithTransformation(any(), any());
 		}
 
 		@Test
 		void shouldGetIdentifier() {
-			runner.runWithDefaultTransformation(AggregationMapping.Scope.INTERN);
+			runner.runWithDefaultTransformation(scope);
 
 			verify(transformationProperties).getIdentifier();
 		}
 
 		@Test
 		void shouldLoadTransformation() {
-			runner.runWithDefaultTransformation(AggregationMapping.Scope.INTERN);
+			runner.runWithDefaultTransformation(scope);
 
 			verify(transformationService).load(identifier, null);
 		}
 
+		@Test
+		void shouldCreateAggregatorForScope() {
+			runner.runWithDefaultTransformation(scope);
+
+			verify(runner).createAggregator(scope);
+		}
+
 		@Test
 		void shouldRunWithTransformation() {
-			runner.runWithDefaultTransformation(AggregationMapping.Scope.INTERN);
+			runner.runWithDefaultTransformation(scope);
 
-			verify(runner).runWithTransformation(eq(transformation), isNull());
+			verify(runner).runWithTransformation(transformation, aggregator);
 		}
 	}
 
 	@Nested
 	class TestRunWithConfiguredTransformations {
 
-		private final String identifier = LoremIpsum.getInstance().getWords(1);
-		private final List<AggregationMapping> aggregationMappings = List.of(AggregationMappingTestFactory.create(),
-				AggregationMappingTestFactory.create());
-
+		private final AggregationMapping.Scope firstScope = AggregationMapping.Scope.INTERN;
 		@Mock
 		private Transformation firstTransformation;
 		@Mock
+		private Aggregator firstAggregator;
+
+		private final AggregationMapping.Scope secondScope = AggregationMapping.Scope.EXTERN;
+		@Mock
 		private Transformation secondTransformation;
+		@Mock
+		private Aggregator secondAggregator;
+
+		private final String identifier = LoremIpsum.getInstance().getWords(1);
+		private final List<AggregationMapping> aggregationMappings = List.of(
+				AggregationMappingTestFactory.createBuilder().scope(firstScope).build(),
+				AggregationMappingTestFactory.createBuilder().scope(secondScope).build());
 
 		@BeforeEach
 		void mock() {
@@ -215,6 +246,10 @@ class AggregationManagerRunnerTest {
 			when(transformationProperties.getIdentifier()).thenReturn(identifier);
 			when(transformationService.load(any(), any())).thenReturn(firstTransformation, secondTransformation);
 			doNothing().when(runner).runWithTransformation(any(), any());
+			doReturn(firstAggregator).when(runner).createAggregator(firstScope);
+			doReturn(secondAggregator).when(runner).createAggregator(secondScope);
+			when(firstAggregator.withAggregationMapping(any())).thenReturn(firstAggregator);
+			when(secondAggregator.withAggregationMapping(any())).thenReturn(secondAggregator);
 		}
 
 		@Test
@@ -231,12 +266,28 @@ class AggregationManagerRunnerTest {
 			aggregationMappings.forEach(mapping -> verify(transformationService).load(identifier, mapping));
 		}
 
+		@Test
+		void shouldCreateAggregatorForScope() {
+			runner.runWithConfiguredTransformations();
+
+			verify(runner).createAggregator(firstScope);
+			verify(runner).createAggregator(secondScope);
+		}
+
+		@Test
+		void shouldSetAggregationMappingInAggregator() {
+			runner.runWithConfiguredTransformations();
+
+			verify(firstAggregator).withAggregationMapping(aggregationMappings.getFirst());
+			verify(secondAggregator).withAggregationMapping(aggregationMappings.getLast());
+		}
+
 		@Test
 		void shouldRunWithTransformationForEachTransformation() {
 			runner.runWithConfiguredTransformations();
 
-			verify(runner).runWithTransformation(firstTransformation, aggregationMappings.get(0));
-			verify(runner).runWithTransformation(secondTransformation, aggregationMappings.get(1));
+			verify(runner).runWithTransformation(firstTransformation, firstAggregator);
+			verify(runner).runWithTransformation(secondTransformation, secondAggregator);
 		}
 	}
 
@@ -245,19 +296,18 @@ class AggregationManagerRunnerTest {
 
 		@Mock
 		private Transformation transformation;
-		private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create();
 		private final ArgumentMatcher<Execution> hasTransformation = execution -> execution.getTransformation().equals(transformation);
 
 		@BeforeEach
-		void mock() {
-			doReturn(aggregator).when(runner).prepareAggregator(any(), any());
+		void init() {
+			when(aggregator.withExecution(any())).thenReturn(aggregator);
 		}
 
 		@Test
-		void shouldCallPrepareAggregator() {
+		void shouldSetExecution() {
 			runWithTransformation();
 
-			verify(runner).prepareAggregator(argThat(hasTransformation), eq(aggregationMapping));
+			verify(aggregator).withExecution(argThat(hasTransformation));
 		}
 
 		@Test
@@ -268,67 +318,67 @@ class AggregationManagerRunnerTest {
 		}
 
 		private void runWithTransformation() {
-			runner.runWithTransformation(transformation, aggregationMapping);
+			runner.runWithTransformation(transformation, aggregator);
 		}
 	}
 
 	@Nested
-	class TestPrepareAggregator {
+	class TestCreateAggregator {
 
-		@Mock
-		private Execution execution;
-		private final AggregationMapping aggregationMapping = AggregationMappingTestFactory.create();
+		private final AggregationMapping.Scope scope = AggregationMapping.Scope.INTERN;
 		private final int batchSize = RandomUtils.insecure().randomInt();
+		@Mock
+		private AggregationDataLoader loader;
 
 		@BeforeEach
-		void mock() {
+		void init() {
 			when(config.getFetchingBatchSize()).thenReturn(batchSize);
-			when(aggregator.withExecution(any())).thenReturn(aggregator);
-			when(aggregator.withAggregationMapping(any())).thenReturn(aggregator);
+			when(loaderRegistry.getLoader(any())).thenReturn(loader);
 			when(aggregator.withBatchSize(anyInt())).thenReturn(aggregator);
+			when(aggregator.withLoader(any())).thenReturn(aggregator);
 		}
 
 		@Test
 		void shouldGetBatchSize() {
-			runner.prepareAggregator(execution, aggregationMapping);
+			runner.createAggregator(scope);
 
 			verify(config).getFetchingBatchSize();
 		}
 
 		@Test
-		void shouldSetExecution() {
-			runner.prepareAggregator(execution, aggregationMapping);
+		void shouldSetBatchSize() {
+			runner.createAggregator(scope);
 
-			verify(aggregator).withExecution(execution);
+			verify(aggregator).withBatchSize(batchSize);
 		}
 
 		@Test
-		void shouldSetAggregationMapping() {
-			runner.prepareAggregator(execution, aggregationMapping);
+		void shouldGetLoaderFromRegistry() {
+			runner.createAggregator(scope);
 
-			verify(aggregator).withAggregationMapping(aggregationMapping);
+			verify(loaderRegistry).getLoader(scope);
 		}
 
 		@Test
-		void shouldSetBatchSize() {
-			runner.prepareAggregator(execution, aggregationMapping);
+		void shouldSetLoader() {
+			runner.createAggregator(scope);
 
-			verify(aggregator).withBatchSize(batchSize);
+			verify(aggregator).withLoader(loader);
 		}
 
 		@Test
 		void shouldReturnAggregator() {
-			var result = runner.prepareAggregator(execution, aggregationMapping);
+			var created = runner.createAggregator(scope);
 
-			assertThat(result).isEqualTo(aggregator);
+			assertThat(created).isEqualTo(aggregator);
 		}
 	}
 
 	static class AggregationManagerRunnerImpl extends AggregationManagerRunner {
 
 		public AggregationManagerRunnerImpl(AggregationManagerConfiguration config, TransformationProperties transformationProperties,
-				TransformationService transformationService) {
-			super(config, transformationProperties, transformationService);
+				TransformationService transformationService, AggregationDataLoaderRegistry loaderRegistry) {
+			super(config, transformationProperties, transformationService, loaderRegistry);
 		}
 
 		@Override
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
index 5af0267..27ce28c 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 import org.junit.jupiter.params.provider.NullAndEmptySource;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
@@ -41,6 +42,7 @@ import org.mockito.Spy;
 
 import com.thedeanda.lorem.LoremIpsum;
 
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.aggregation.warehouse.DocumentEntry;
 import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
 
@@ -107,4 +109,23 @@ class AggregationWarehouseDataLoaderTest {
 			assertThat(collectionName).isEqualTo(DocumentEntry.COLLECTION);
 		}
 	}
+
+	@Nested
+	class TestSupportsScope {
+
+		@ParameterizedTest
+		@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = "INTERN")
+		void shouldReturnFalse(AggregationMapping.Scope scope) {
+			var supported = loader.supportsScope(scope);
+
+			assertThat(supported).isFalse();
+		}
+
+		@Test
+		void shouldReturnTrue() {
+			var supported = loader.supportsScope(AggregationMapping.Scope.INTERN);
+
+			assertThat(supported).isTrue();
+		}
+	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
index e6db338..e76d39f 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
@@ -34,12 +34,15 @@ import java.util.concurrent.Future;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
 
 import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationTestFactory;
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
 import lombok.SneakyThrows;
 
@@ -124,4 +127,23 @@ class AggregationRemoteDataLoaderTest {
 			}
 		}
 	}
+
+	@Nested
+	class TestSupportsScope {
+
+		@ParameterizedTest
+		@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = "EXTERN")
+		void shouldReturnFalse(AggregationMapping.Scope scope) {
+			var supported = loader.supportsScope(scope);
+
+			assertThat(supported).isFalse();
+		}
+
+		@Test
+		void shouldReturnTrue() {
+			var supported = loader.supportsScope(AggregationMapping.Scope.EXTERN);
+
+			assertThat(supported).isTrue();
+		}
+	}
 }
-- 
GitLab


From 9f5452affb743783d8eed833b1aa34aa998cea69 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Mon, 28 Apr 2025 16:11:21 +0200
Subject: [PATCH 09/14] OZG-7811 OZG-8099 Registration of loaders

---
 .../aggregation/AggregationDataLoader.java    |  4 --
 .../AggregationDataLoaderRegistry.java        | 23 ++------
 .../AggregationWarehouseDataLoader.java       |  9 +--
 .../extern/AggregationRemoteDataLoader.java   |  8 +--
 .../AggregationDataLoaderRegistryTest.java    | 58 ++++---------------
 .../AggregationWarehouseDataLoaderTest.java   | 18 ++----
 .../AggregationRemoteDataLoaderTest.java      | 19 ++----
 7 files changed, 30 insertions(+), 109 deletions(-)

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
index f116b96..0bd3bd6 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
@@ -23,11 +23,7 @@
  */
 package de.ozgcloud.aggregation;
 
-import de.ozgcloud.aggregation.transformation.AggregationMapping;
-
 public interface AggregationDataLoader {
 
 	void loadIntoTarget(Aggregation aggregation);
-
-	boolean supportsScope(AggregationMapping.Scope scope);
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
index 363333b..4d7e2ff 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
@@ -23,12 +23,8 @@
  */
 package de.ozgcloud.aggregation;
 
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.function.Predicate;
-
-import javax.annotation.PostConstruct;
+import java.util.Optional;
 
 import org.springframework.stereotype.Component;
 
@@ -40,24 +36,13 @@ import lombok.RequiredArgsConstructor;
 @RequiredArgsConstructor
 class AggregationDataLoaderRegistry {
 
-	private final List<AggregationDataLoader> loaders;
-	private final Map<AggregationMapping.Scope, AggregationDataLoader> scopeToLoader = new HashMap<>();
-
-	@PostConstruct
-	void buildLoadersMap() {
-
-	}
+	private final Map<String, AggregationDataLoader> loaders;
 
 	public boolean hasLoader(AggregationMapping.Scope scope) {
-		return loaders.stream().anyMatch(supportsScope(scope));
+		return loaders.containsKey(scope.name());
 	}
 
 	public AggregationDataLoader getLoader(AggregationMapping.Scope scope) {
-		return loaders.stream().filter(supportsScope(scope)).findFirst()
-				.orElseThrow(() -> new TechnicalException("No loader found for scope " + scope));
-	}
-
-	private Predicate<AggregationDataLoader> supportsScope(AggregationMapping.Scope scope) {
-		return loader -> loader.supportsScope(scope);
+		return Optional.ofNullable(loaders.get(scope.name())).orElseThrow(() -> new TechnicalException("No data loader for scope " + scope));
 	}
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
index 5694975..51ead0e 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
@@ -24,19 +24,21 @@
 package de.ozgcloud.aggregation;
 
 import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
-import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.aggregation.warehouse.DocumentEntry;
 import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
 @Component
+@Qualifier(AggregationWarehouseDataLoader.SCOPE)
 @RequiredArgsConstructor
 @Log4j2
 public class AggregationWarehouseDataLoader implements AggregationDataLoader {
 
+	static final String SCOPE = "INTERN";
 	private final WarehouseRepository repository;
 
 	@Override
@@ -49,9 +51,4 @@ public class AggregationWarehouseDataLoader implements AggregationDataLoader {
 	String getCollectionName(Aggregation aggregation) {
 		return StringUtils.isNotBlank(aggregation.aggregationName()) ? aggregation.aggregationName() : DocumentEntry.COLLECTION;
 	}
-
-	@Override
-	public boolean supportsScope(AggregationMapping.Scope scope) {
-		return AggregationMapping.Scope.INTERN.equals(scope);
-	}
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
index 29ed0db..12a9e4d 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
@@ -25,22 +25,24 @@ package de.ozgcloud.aggregation.extern;
 
 import java.util.concurrent.ExecutionException;
 
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Component;
 
 import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationDataLoader;
-import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
 @Component
+@Qualifier(AggregationRemoteDataLoader.SCOPE)
 @ConditionalOnProperty("grpc.client.aggregation-manager.address")
 @RequiredArgsConstructor
 @Log4j2
 public class AggregationRemoteDataLoader implements AggregationDataLoader {
 
+	static final String SCOPE = "EXTERN";
 	private final AggregationDataRemoteService service;
 
 	@Override
@@ -54,8 +56,4 @@ public class AggregationRemoteDataLoader implements AggregationDataLoader {
 			throw new TechnicalException("Error on sending aggregation data.", e);
 		}
 	}
-
-	public boolean supportsScope(AggregationMapping.Scope scope) {
-		return AggregationMapping.Scope.EXTERN.equals(scope);
-	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
index cd7127d..02d5b4b 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
@@ -26,7 +26,6 @@ package de.ozgcloud.aggregation;
 import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
-import java.util.List;
 import java.util.Map;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -36,58 +35,36 @@ import org.mockito.Mock;
 
 import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
-import de.ozgcloud.common.test.ReflectionTestUtils;
 
 class AggregationDataLoaderRegistryTest {
 
+	private static final String SCOPE_KEY = AggregationWarehouseDataLoader.SCOPE;
+	private final AggregationMapping.Scope registeredScope = AggregationMapping.Scope.INTERN;
+	private final AggregationMapping.Scope notRegisteredScope = AggregationMapping.Scope.EXTERN;
+
+
 	@Mock
 	private AggregationDataLoader loader;
 	private AggregationDataLoaderRegistry registry;
 
 	@BeforeEach
 	void init() {
-		registry = spy(new AggregationDataLoaderRegistry(List.of(loader)));
-	}
-
-	@Nested
-	class TestBuildLoadersMap {
-
-		@Test
-		void shouldAddLoader() {
-		}
-
-		@SuppressWarnings("unchecked")
-		private Map<AggregationMapping.Scope, AggregationDataLoader> getScopeToLoader() {
-			return ReflectionTestUtils.getField(registry, "scopeToLoader", Map.class);
-		}
+		registry = spy(new AggregationDataLoaderRegistry(Map.of(SCOPE_KEY, loader)));
 	}
 
 	@Nested
 	class TestHasLoader {
 
-		@Test
-		void shouldCheckIfLoaderSupportsScope() {
-			var scope = AggregationMapping.Scope.INTERN;
-
-			registry.hasLoader(scope);
-
-			verify(loader).supportsScope(scope);
-		}
-
 		@Test
 		void shouldReturnFalseIfNoLoaderRegisteredForScope() {
-			when(loader.supportsScope(any())).thenReturn(false);
-
-			var hasLoader = registry.hasLoader(AggregationMapping.Scope.INTERN);
+			var hasLoader = registry.hasLoader(notRegisteredScope);
 
 			assertThat(hasLoader).isFalse();
 		}
 
 		@Test
 		void shouldReturnTrueIfLoaderRegisteredForScope() {
-			when(loader.supportsScope(any())).thenReturn(true);
-
-			var hasLoader = registry.hasLoader(AggregationMapping.Scope.INTERN);
+			var hasLoader = registry.hasLoader(registeredScope);
 
 			assertThat(hasLoader).isTrue();
 		}
@@ -96,31 +73,16 @@ class AggregationDataLoaderRegistryTest {
 	@Nested
 	class TestGetLoader {
 
-		private final AggregationMapping.Scope scope = AggregationMapping.Scope.INTERN;
-
-		@Test
-		void shouldCheckIfLoaderSupportsScope() {
-			when(loader.supportsScope(any())).thenReturn(true);
-
-			registry.getLoader(scope);
-
-			verify(loader).supportsScope(scope);
-		}
-
 		@Test
 		void shouldReturnLoaderThatSupportsScope() {
-			when(loader.supportsScope(any())).thenReturn(true);
-
-			var loaderSupportingScope = registry.getLoader(scope);
+			var loaderSupportingScope = registry.getLoader(registeredScope);
 
 			assertThat(loaderSupportingScope).isSameAs(loader);
 		}
 
 		@Test
 		void shouldThrowExceptionIfNoLoaderRegisteredForScope() {
-			when(loader.supportsScope(any())).thenReturn(false);
-
-			assertThatThrownBy(() -> registry.getLoader(scope)).isInstanceOf(TechnicalException.class);
+			assertThatThrownBy(() -> registry.getLoader(notRegisteredScope)).isInstanceOf(TechnicalException.class);
 		}
 	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
index 27ce28c..9cd225c 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
@@ -32,13 +32,13 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.EnumSource;
 import org.junit.jupiter.params.provider.NullAndEmptySource;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
+import org.springframework.beans.factory.annotation.Qualifier;
 
 import com.thedeanda.lorem.LoremIpsum;
 
@@ -111,21 +111,13 @@ class AggregationWarehouseDataLoaderTest {
 	}
 
 	@Nested
-	class TestSupportsScope {
-
-		@ParameterizedTest
-		@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = "INTERN")
-		void shouldReturnFalse(AggregationMapping.Scope scope) {
-			var supported = loader.supportsScope(scope);
-
-			assertThat(supported).isFalse();
-		}
+	class TestQualifier {
 
 		@Test
-		void shouldReturnTrue() {
-			var supported = loader.supportsScope(AggregationMapping.Scope.INTERN);
+		void shouldBeIntern() {
+			var qualifierValue = AggregationWarehouseDataLoader.class.getAnnotation(Qualifier.class).value();
 
-			assertThat(supported).isTrue();
+			assertThat(qualifierValue).isEqualTo(AggregationMapping.Scope.INTERN.name());
 		}
 	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
index e76d39f..49e0b07 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
@@ -34,11 +34,10 @@ import java.util.concurrent.Future;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.EnumSource;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
+import org.springframework.beans.factory.annotation.Qualifier;
 
 import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationTestFactory;
@@ -129,21 +128,13 @@ class AggregationRemoteDataLoaderTest {
 	}
 
 	@Nested
-	class TestSupportsScope {
-
-		@ParameterizedTest
-		@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = "EXTERN")
-		void shouldReturnFalse(AggregationMapping.Scope scope) {
-			var supported = loader.supportsScope(scope);
-
-			assertThat(supported).isFalse();
-		}
+	class TestQualifier {
 
 		@Test
-		void shouldReturnTrue() {
-			var supported = loader.supportsScope(AggregationMapping.Scope.EXTERN);
+		void shouldBeExtern() {
+			var qualifierValue = AggregationRemoteDataLoader.class.getAnnotation(Qualifier.class).value();
 
-			assertThat(supported).isTrue();
+			assertThat(qualifierValue).isEqualTo(AggregationMapping.Scope.EXTERN.name());
 		}
 	}
 }
-- 
GitLab


From e0836d6439287859275533abec2e50fe0aa577de Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Tue, 29 Apr 2025 15:24:45 +0200
Subject: [PATCH 10/14] OZG-7811 OZG-8099 Working registration of loaders

---
 .../aggregation/AggregationDataLoader.java    |  4 +
 .../AggregationDataLoaderRegistry.java        | 25 ++++--
 .../AggregationManagerGrpcConfiguration.java  | 13 +++
 .../extern/AggregationDataRemoteService.java  |  7 +-
 .../extern/AggregationRemoteDataLoader.java   | 10 ++-
 .../extern/GrpcAggregationDataMapper.java     |  2 +-
 .../AggregationWarehouseDataLoader.java       | 15 ++--
 .../AggregationDataLoaderRegistryTest.java    | 75 ++++++++++++++--
 .../AggregationRemoteDataLoaderTest.java      |  9 +-
 .../extern/SpringContextITCase.java           | 88 +++++++++++++++++++
 .../AggregationWarehouseDataLoaderTest.java   | 15 ++--
 .../warehouse/SpringContextITCase.java        | 61 +++++++++++++
 12 files changed, 283 insertions(+), 41 deletions(-)
 rename aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/{ => warehouse}/AggregationWarehouseDataLoader.java (84%)
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java
 rename aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/{ => warehouse}/AggregationWarehouseDataLoaderTest.java (88%)
 create mode 100644 aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
index 0bd3bd6..454b0ad 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoader.java
@@ -23,7 +23,11 @@
  */
 package de.ozgcloud.aggregation;
 
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
+
 public interface AggregationDataLoader {
 
 	void loadIntoTarget(Aggregation aggregation);
+
+	AggregationMapping.Scope getScope();
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
index 4d7e2ff..614e35a 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistry.java
@@ -23,26 +23,39 @@
  */
 package de.ozgcloud.aggregation;
 
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import org.springframework.stereotype.Component;
 
 import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
-import lombok.RequiredArgsConstructor;
 
 @Component
-@RequiredArgsConstructor
-class AggregationDataLoaderRegistry {
+public class AggregationDataLoaderRegistry {
 
-	private final Map<String, AggregationDataLoader> loaders;
+	private final Map<AggregationMapping.Scope, AggregationDataLoader> loadersByScope;
+
+	AggregationDataLoaderRegistry(List<AggregationDataLoader> loaders) {
+		this.loadersByScope = toLoadersByScope(loaders);
+	}
 
 	public boolean hasLoader(AggregationMapping.Scope scope) {
-		return loaders.containsKey(scope.name());
+		return loadersByScope.containsKey(scope);
 	}
 
 	public AggregationDataLoader getLoader(AggregationMapping.Scope scope) {
-		return Optional.ofNullable(loaders.get(scope.name())).orElseThrow(() -> new TechnicalException("No data loader for scope " + scope));
+		return Optional.ofNullable(loadersByScope.get(scope)).orElseThrow(() -> new TechnicalException("No data loader for scope " + scope));
+	}
+
+	static Map<AggregationMapping.Scope, AggregationDataLoader> toLoadersByScope(List<AggregationDataLoader> loaders) {
+		try {
+			return loaders.stream().collect(Collectors.toMap(AggregationDataLoader::getScope, Function.identity()));
+		} catch (IllegalStateException e) {
+			throw new TechnicalException("Multiple loaders exist for single scope.", e);
+		}
 	}
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java
index eec32d5..a9b23c1 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java
@@ -23,9 +23,13 @@
  */
 package de.ozgcloud.aggregation;
 
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
+import de.ozgcloud.aggregation.extern.AggregationDataRemoteService;
+import de.ozgcloud.aggregation.extern.GrpcAggregationDataMapper;
 import de.ozgcloud.apilib.vorgang.OzgCloudVorgangService;
 import de.ozgcloud.apilib.vorgang.grpc.GrpcOzgCloudVorgangService;
 import de.ozgcloud.apilib.vorgang.grpc.OzgCloudVorgangMapper;
@@ -37,9 +41,12 @@ import net.devh.boot.grpc.client.inject.GrpcClientBean;
 
 @Configuration
 @GrpcClientBean(clazz = VorgangServiceGrpc.VorgangServiceBlockingStub.class, beanName = "vorgangServiceBlockingStub", client = @GrpcClient("vorgang-manager"))
+@GrpcClientBean(clazz = AggregationDataServiceGrpc.AggregationDataServiceStub.class, beanName = "aggregationDataServiceStub", client = @GrpcClient("aggregation-manager"))
 public class AggregationManagerGrpcConfiguration {
 	@GrpcClient("vorgang-manager")
 	VorgangServiceGrpc.VorgangServiceBlockingStub vorgangServiceBlockingStub;
+	@GrpcClient("aggregation-manager")
+	AggregationDataServiceGrpc.AggregationDataServiceStub aggregationDataServiceStub;
 
 	@Bean
 	OzgCloudVorgangService grpcOzgCloudVorgangService(OzgCloudVorgangMapper vorgangMapper,
@@ -49,4 +56,10 @@ public class AggregationManagerGrpcConfiguration {
 				aggregationCallContext);
 	}
 
+	@Bean
+	@ConditionalOnProperty("grpc.client.aggregation-manager.address")
+	AggregationDataRemoteService aggregationDataRemoteService(AggregationManagerProperties properties,
+			AggregationManagerConfiguration configuration, GrpcAggregationDataMapper grpcAggregationDataMapper) {
+		return new AggregationDataRemoteService(aggregationDataServiceStub, properties, configuration, grpcAggregationDataMapper);
+	}
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
index 564df21..531a02c 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
@@ -28,9 +28,6 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 import java.util.stream.Stream;
 
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.stereotype.Service;
-
 import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationManagerConfiguration;
 import de.ozgcloud.aggregation.AggregationManagerProperties;
@@ -46,10 +43,8 @@ import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import net.devh.boot.grpc.client.inject.GrpcClient;
 
-@Service
-@ConditionalOnProperty("grpc.client.aggregation-manager.address")
 @RequiredArgsConstructor
-class AggregationDataRemoteService {
+public class AggregationDataRemoteService {
 
 	@GrpcClient("aggregation-manager")
 	private final AggregationDataServiceGrpc.AggregationDataServiceStub serviceStub;
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
index 12a9e4d..845c203 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
@@ -25,24 +25,22 @@ package de.ozgcloud.aggregation.extern;
 
 import java.util.concurrent.ExecutionException;
 
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Component;
 
 import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationDataLoader;
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
 @Component
-@Qualifier(AggregationRemoteDataLoader.SCOPE)
 @ConditionalOnProperty("grpc.client.aggregation-manager.address")
 @RequiredArgsConstructor
 @Log4j2
 public class AggregationRemoteDataLoader implements AggregationDataLoader {
 
-	static final String SCOPE = "EXTERN";
 	private final AggregationDataRemoteService service;
 
 	@Override
@@ -56,4 +54,10 @@ public class AggregationRemoteDataLoader implements AggregationDataLoader {
 			throw new TechnicalException("Error on sending aggregation data.", e);
 		}
 	}
+
+	@Override
+	public AggregationMapping.Scope getScope() {
+		return AggregationMapping.Scope.EXTERN;
+	}
+
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java
index d111d82..33bdee4 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/GrpcAggregationDataMapper.java
@@ -30,7 +30,7 @@ import de.ozgcloud.aggregation.data.GrpcAggregationData;
 import de.ozgcloud.aggregation.warehouse.DocumentEntry;
 
 @Mapper
-interface GrpcAggregationDataMapper {
+public interface GrpcAggregationDataMapper {
 
 	@Mapping(target = "eingangDatum", source = "eingangsdatum")
 	@Mapping(target = "vorgangName", source = "vorgangsname")
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoader.java
similarity index 84%
rename from aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
rename to aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoader.java
index 51ead0e..3e319bf 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoader.java
@@ -21,24 +21,22 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-package de.ozgcloud.aggregation;
+package de.ozgcloud.aggregation.warehouse;
 
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Component;
 
-import de.ozgcloud.aggregation.warehouse.DocumentEntry;
-import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
+import de.ozgcloud.aggregation.Aggregation;
+import de.ozgcloud.aggregation.AggregationDataLoader;
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.log4j.Log4j2;
 
 @Component
-@Qualifier(AggregationWarehouseDataLoader.SCOPE)
 @RequiredArgsConstructor
 @Log4j2
 public class AggregationWarehouseDataLoader implements AggregationDataLoader {
 
-	static final String SCOPE = "INTERN";
 	private final WarehouseRepository repository;
 
 	@Override
@@ -51,4 +49,9 @@ public class AggregationWarehouseDataLoader implements AggregationDataLoader {
 	String getCollectionName(Aggregation aggregation) {
 		return StringUtils.isNotBlank(aggregation.aggregationName()) ? aggregation.aggregationName() : DocumentEntry.COLLECTION;
 	}
+
+	@Override
+	public AggregationMapping.Scope getScope() {
+		return AggregationMapping.Scope.INTERN;
+	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
index 02d5b4b..92cde4b 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationDataLoaderRegistryTest.java
@@ -26,30 +26,61 @@ package de.ozgcloud.aggregation;
 import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
 
+import java.util.List;
 import java.util.Map;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mock;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.mockito.MockedStatic;
 
 import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
+import de.ozgcloud.common.test.ReflectionTestUtils;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 
 class AggregationDataLoaderRegistryTest {
 
-	private static final String SCOPE_KEY = AggregationWarehouseDataLoader.SCOPE;
 	private final AggregationMapping.Scope registeredScope = AggregationMapping.Scope.INTERN;
 	private final AggregationMapping.Scope notRegisteredScope = AggregationMapping.Scope.EXTERN;
 
-
-	@Mock
-	private AggregationDataLoader loader;
+	private final AggregationDataLoader loader = new TestLoader(registeredScope);
 	private AggregationDataLoaderRegistry registry;
 
 	@BeforeEach
 	void init() {
-		registry = spy(new AggregationDataLoaderRegistry(Map.of(SCOPE_KEY, loader)));
+		registry = spy(new AggregationDataLoaderRegistry(List.of(loader)));
+	}
+
+	@Nested
+	class TestConstructor {
+
+		private final List<AggregationDataLoader> loaders = List.of(new TestLoader(AggregationMapping.Scope.INTERN));
+
+		@Test
+		void shouldConvertToLoadersByScope() {
+			try (MockedStatic<AggregationDataLoaderRegistry> mocked = mockStatic(AggregationDataLoaderRegistry.class)) {
+				new AggregationDataLoaderRegistry(loaders);
+				mocked.verify(() -> AggregationDataLoaderRegistry.toLoadersByScope(loaders));
+			}
+		}
+
+		@Test
+		@SuppressWarnings("unchecked")
+		void setLoadersByScope() {
+			var loadersByScope = Map.of(AggregationMapping.Scope.INTERN, new TestLoader(AggregationMapping.Scope.INTERN));
+			try (MockedStatic<AggregationDataLoaderRegistry> mocked = mockStatic(AggregationDataLoaderRegistry.class)) {
+				mocked.when(() -> AggregationDataLoaderRegistry.toLoadersByScope(loaders)).thenReturn(loadersByScope);
+
+				AggregationDataLoaderRegistry registry = new AggregationDataLoaderRegistry(loaders);
+
+				var loadersByScopeFieldValue = ReflectionTestUtils.getField(registry, "loadersByScope", Map.class);
+				assertThat(loadersByScopeFieldValue).isEqualTo(loadersByScope);
+			}
+		}
 	}
 
 	@Nested
@@ -85,4 +116,36 @@ class AggregationDataLoaderRegistryTest {
 			assertThatThrownBy(() -> registry.getLoader(notRegisteredScope)).isInstanceOf(TechnicalException.class);
 		}
 	}
+
+	@Nested
+	class TestToLoadersByScope {
+
+		@ParameterizedTest
+		@EnumSource
+		void shouldAddLoader(AggregationMapping.Scope scope) {
+			var loader = new TestLoader(scope);
+			var loadersByScope = AggregationDataLoaderRegistry.toLoadersByScope(List.of(loader));
+
+			assertThat(loadersByScope).containsEntry(scope, loader);
+		}
+
+		@Test
+		void shouldThrowExceptionIfMultipleLoadersHaveSameScope() {
+			var scope = AggregationMapping.Scope.EXTERN;
+			assertThatThrownBy(() -> AggregationDataLoaderRegistry.toLoadersByScope(List.of(new TestLoader(scope), new TestLoader(scope))))
+					.isInstanceOf(TechnicalException.class);
+		}
+	}
+
+	@RequiredArgsConstructor
+	static class TestLoader implements AggregationDataLoader {
+
+		@Getter
+		private final AggregationMapping.Scope scope;
+
+		@Override
+		public void loadIntoTarget(Aggregation aggregation) {
+
+		}
+	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
index 49e0b07..045febe 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
@@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
-import org.springframework.beans.factory.annotation.Qualifier;
 
 import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationTestFactory;
@@ -128,13 +127,13 @@ class AggregationRemoteDataLoaderTest {
 	}
 
 	@Nested
-	class TestQualifier {
+	class TestGetScope {
 
 		@Test
-		void shouldBeExtern() {
-			var qualifierValue = AggregationRemoteDataLoader.class.getAnnotation(Qualifier.class).value();
+		void shouldReturnExternScope() {
+			var scope = loader.getScope();
 
-			assertThat(qualifierValue).isEqualTo(AggregationMapping.Scope.EXTERN.name());
+			assertThat(scope).isEqualTo(AggregationMapping.Scope.EXTERN);
 		}
 	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java
new file mode 100644
index 0000000..2c7e93e
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.extern;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.TestPropertySource;
+
+import de.ozgcloud.aggregation.AggregationDataLoader;
+import de.ozgcloud.aggregation.AggregationDataLoaderRegistry;
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
+import de.ozgcloud.common.test.ITCase;
+
+class SpringContextITCase {
+
+	@Nested
+	@ITCase
+	@TestPropertySource(properties = { "grpc.client.aggregation-manager.address=static://127.0.0.1:9090" })
+	class OnAggregationManagerAddressSet {
+
+		@Autowired
+		private List<AggregationDataLoader> loaders;
+		@Autowired
+		private AggregationDataLoaderRegistry registry;
+
+		@Test
+		void shouldHaveOneLoader() {
+			assertThat(getLoadersWithScopeExtern(loaders)).singleElement().isInstanceOf(AggregationRemoteDataLoader.class);
+		}
+
+		@Test
+		void shouldRegister() {
+			assertThat(registry.hasLoader(AggregationMapping.Scope.EXTERN)).isTrue();
+		}
+	}
+
+	@Nested
+	@ITCase
+	class OnAggregationManagerAddressNotSet {
+
+		@Autowired
+		private List<AggregationDataLoader> loaders;
+		@Autowired
+		private AggregationDataLoaderRegistry registry;
+
+		@Test
+		void shouldNotHaveLoader() {
+			assertThat(getLoadersWithScopeExtern(loaders)).isEmpty();
+		}
+
+		@Test
+		void shouldNotRegister() {
+			assertThat(registry.hasLoader(AggregationMapping.Scope.EXTERN)).isFalse();
+		}
+	}
+
+	private List<AggregationDataLoader> getLoadersWithScopeExtern(List<AggregationDataLoader> loaders) {
+		return loaders.stream()
+				.filter(loader -> loader.getScope() == AggregationMapping.Scope.EXTERN)
+				.toList();
+	}
+}
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoaderTest.java
similarity index 88%
rename from aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
rename to aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoaderTest.java
index 9cd225c..ed86396 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationWarehouseDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoaderTest.java
@@ -21,7 +21,7 @@
  * Die sprachspezifischen Genehmigungen und Beschränkungen
  * unter der Lizenz sind dem Lizenztext zu entnehmen.
  */
-package de.ozgcloud.aggregation;
+package de.ozgcloud.aggregation.warehouse;
 
 import static org.assertj.core.api.Assertions.*;
 import static org.mockito.Mockito.*;
@@ -38,13 +38,12 @@ import org.mockito.Captor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Spy;
-import org.springframework.beans.factory.annotation.Qualifier;
 
 import com.thedeanda.lorem.LoremIpsum;
 
+import de.ozgcloud.aggregation.Aggregation;
+import de.ozgcloud.aggregation.AggregationTestFactory;
 import de.ozgcloud.aggregation.transformation.AggregationMapping;
-import de.ozgcloud.aggregation.warehouse.DocumentEntry;
-import de.ozgcloud.aggregation.warehouse.WarehouseRepository;
 
 class AggregationWarehouseDataLoaderTest {
 
@@ -111,13 +110,13 @@ class AggregationWarehouseDataLoaderTest {
 	}
 
 	@Nested
-	class TestQualifier {
+	class TestGetScope {
 
 		@Test
-		void shouldBeIntern() {
-			var qualifierValue = AggregationWarehouseDataLoader.class.getAnnotation(Qualifier.class).value();
+		void shouldReturnInternScope() {
+			var scope = loader.getScope();
 
-			assertThat(qualifierValue).isEqualTo(AggregationMapping.Scope.INTERN.name());
+			assertThat(scope).isEqualTo(AggregationMapping.Scope.INTERN);
 		}
 	}
 }
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java
new file mode 100644
index 0000000..ed88f4f
--- /dev/null
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
+ * Ministerpräsidenten des Landes Schleswig-Holstein
+ * Staatskanzlei
+ * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
+ *
+ * Lizenziert unter der EUPL, Version 1.2 oder - sobald
+ * diese von der Europäischen Kommission genehmigt wurden -
+ * Folgeversionen der EUPL ("Lizenz");
+ * Sie dürfen dieses Werk ausschließlich gemäß
+ * dieser Lizenz nutzen.
+ * Eine Kopie der Lizenz finden Sie hier:
+ *
+ * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
+ *
+ * Sofern nicht durch anwendbare Rechtsvorschriften
+ * gefordert oder in schriftlicher Form vereinbart, wird
+ * die unter der Lizenz verbreitete Software "so wie sie
+ * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
+ * ausdrücklich oder stillschweigend - verbreitet.
+ * Die sprachspezifischen Genehmigungen und Beschränkungen
+ * unter der Lizenz sind dem Lizenztext zu entnehmen.
+ */
+package de.ozgcloud.aggregation.warehouse;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import de.ozgcloud.aggregation.AggregationDataLoader;
+import de.ozgcloud.aggregation.AggregationDataLoaderRegistry;
+import de.ozgcloud.aggregation.transformation.AggregationMapping;
+import de.ozgcloud.common.test.ITCase;
+
+@ITCase
+class SpringContextITCase {
+
+	@Autowired
+	private List<AggregationDataLoader> loaders;
+	@Autowired
+	private AggregationDataLoaderRegistry registry;
+
+	@Test
+	void shouldHaveLoader() {
+		assertThat(getLoadersWithScopeIntern(loaders)).singleElement().isInstanceOf(AggregationWarehouseDataLoader.class);
+	}
+
+	@Test
+	void shouldRegister() {
+		assertThat(registry.hasLoader(AggregationMapping.Scope.INTERN)).isTrue();
+	}
+
+	private List<AggregationDataLoader> getLoadersWithScopeIntern(List<AggregationDataLoader> loaders) {
+		return loaders.stream()
+				.filter(loader -> loader.getScope() == AggregationMapping.Scope.INTERN)
+				.toList();
+	}
+}
-- 
GitLab


From b6c33db163d85fade99a225cf85c9abe853eb2c7 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Tue, 29 Apr 2025 16:14:11 +0200
Subject: [PATCH 11/14] OZG-7811 OZG-8114 Adjust properties

---
 .../AggregationManagerConfiguration.java      | 38 -------------------
 .../AggregationManagerGrpcConfiguration.java  |  6 +--
 .../AggregationManagerProperties.java         |  1 +
 .../aggregation/AggregationManagerRunner.java |  4 +-
 .../extern/AggregationDataRemoteService.java  |  4 +-
 .../src/main/resources/application-local.yml  |  8 +---
 .../src/main/resources/application.yml        |  5 ++-
 .../AggregationManagerRunnerTest.java         | 10 ++---
 .../AggregationDataRemoteServiceTest.java     |  7 +---
 .../src/test/resources/application-itcase.yml |  9 ++---
 10 files changed, 22 insertions(+), 70 deletions(-)
 delete mode 100644 aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerConfiguration.java

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerConfiguration.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerConfiguration.java
deleted file mode 100644
index 0054187..0000000
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerConfiguration.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
- * Ministerpräsidenten des Landes Schleswig-Holstein
- * Staatskanzlei
- * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
- *
- * Lizenziert unter der EUPL, Version 1.2 oder - sobald
- * diese von der Europäischen Kommission genehmigt wurden -
- * Folgeversionen der EUPL ("Lizenz");
- * Sie dürfen dieses Werk ausschließlich gemäß
- * dieser Lizenz nutzen.
- * Eine Kopie der Lizenz finden Sie hier:
- *
- * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- *
- * Sofern nicht durch anwendbare Rechtsvorschriften
- * gefordert oder in schriftlicher Form vereinbart, wird
- * die unter der Lizenz verbreitete Software "so wie sie
- * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
- * ausdrücklich oder stillschweigend - verbreitet.
- * Die sprachspezifischen Genehmigungen und Beschränkungen
- * unter der Lizenz sind dem Lizenztext zu entnehmen.
- */
-package de.ozgcloud.aggregation;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@ConfigurationProperties(prefix = "aggregation-manager")
-@Getter
-@Setter
-public class AggregationManagerConfiguration {
-
-	private int fetchingBatchSize;
-
-}
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java
index a9b23c1..3994100 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerGrpcConfiguration.java
@@ -43,6 +43,7 @@ import net.devh.boot.grpc.client.inject.GrpcClientBean;
 @GrpcClientBean(clazz = VorgangServiceGrpc.VorgangServiceBlockingStub.class, beanName = "vorgangServiceBlockingStub", client = @GrpcClient("vorgang-manager"))
 @GrpcClientBean(clazz = AggregationDataServiceGrpc.AggregationDataServiceStub.class, beanName = "aggregationDataServiceStub", client = @GrpcClient("aggregation-manager"))
 public class AggregationManagerGrpcConfiguration {
+
 	@GrpcClient("vorgang-manager")
 	VorgangServiceGrpc.VorgangServiceBlockingStub vorgangServiceBlockingStub;
 	@GrpcClient("aggregation-manager")
@@ -58,8 +59,7 @@ public class AggregationManagerGrpcConfiguration {
 
 	@Bean
 	@ConditionalOnProperty("grpc.client.aggregation-manager.address")
-	AggregationDataRemoteService aggregationDataRemoteService(AggregationManagerProperties properties,
-			AggregationManagerConfiguration configuration, GrpcAggregationDataMapper grpcAggregationDataMapper) {
-		return new AggregationDataRemoteService(aggregationDataServiceStub, properties, configuration, grpcAggregationDataMapper);
+	AggregationDataRemoteService aggregationDataRemoteService(AggregationManagerProperties properties, GrpcAggregationDataMapper grpcAggregationDataMapper) {
+		return new AggregationDataRemoteService(aggregationDataServiceStub, properties, grpcAggregationDataMapper);
 	}
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java
index 4ace2f5..c54be5d 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerProperties.java
@@ -39,5 +39,6 @@ import lombok.extern.log4j.Log4j2;
 @Log4j2
 public class AggregationManagerProperties {
 
+	private int fetchingBatchSize;
 	private String mandant;
 }
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
index 9debd13..8b0544e 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/AggregationManagerRunner.java
@@ -49,7 +49,7 @@ public abstract class AggregationManagerRunner implements CommandLineRunner {
 
 	private static final String MDC_EXECUTION = "execution";
 
-	private final AggregationManagerConfiguration config;
+	private final AggregationManagerProperties aggregationManagerProperties;
 	private final TransformationProperties transformationProperties;
 	private final TransformationService transformationService;
 	private final AggregationDataLoaderRegistry loaderRegistry;
@@ -91,7 +91,7 @@ public abstract class AggregationManagerRunner implements CommandLineRunner {
 
 	Aggregator createAggregator(AggregationMapping.Scope scope) {
 		return createAggregator()
-				.withBatchSize(config.getFetchingBatchSize())
+				.withBatchSize(aggregationManagerProperties.getFetchingBatchSize())
 				.withLoader(loaderRegistry.getLoader(scope));
 	}
 
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
index 531a02c..b0b91dc 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteService.java
@@ -29,7 +29,6 @@ import java.util.concurrent.Future;
 import java.util.stream.Stream;
 
 import de.ozgcloud.aggregation.Aggregation;
-import de.ozgcloud.aggregation.AggregationManagerConfiguration;
 import de.ozgcloud.aggregation.AggregationManagerProperties;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
 import de.ozgcloud.aggregation.data.GrpcAggregationData;
@@ -49,7 +48,6 @@ public class AggregationDataRemoteService {
 	@GrpcClient("aggregation-manager")
 	private final AggregationDataServiceGrpc.AggregationDataServiceStub serviceStub;
 	private final AggregationManagerProperties properties;
-	private final AggregationManagerConfiguration configuration;
 	private final GrpcAggregationDataMapper grpcAggregationDataMapper;
 
 	public Future<Void> sendAggregationData(Aggregation aggregation) {
@@ -61,7 +59,7 @@ public class AggregationDataRemoteService {
 	SendAggregationDataResponseObserver buildSendAggregationDataResponseObserver(Aggregation aggregation) {
 		var requestData = new RequestData(properties.getMandant(), aggregation.aggregationName(),
 				toGrpcAggregationDataStream(aggregation.documentEntries()).iterator());
-		return new SendAggregationDataResponseObserver(configuration.getFetchingBatchSize(), requestData);
+		return new SendAggregationDataResponseObserver(properties.getFetchingBatchSize(), requestData);
 	}
 
 	Stream<GrpcAggregationData> toGrpcAggregationDataStream(Stream<DocumentEntry> documentEntries) {
diff --git a/aggregation-manager-job/src/main/resources/application-local.yml b/aggregation-manager-job/src/main/resources/application-local.yml
index f971794..a038ff2 100644
--- a/aggregation-manager-job/src/main/resources/application-local.yml
+++ b/aggregation-manager-job/src/main/resources/application-local.yml
@@ -14,10 +14,6 @@ spring:
       database: aggregation-manager-job
 
 ozgcloud:
-  vorgang-manager:
-    address: static://127.0.0.1:9090
-    negotiationType: PLAINTEXT
-
-aggregation-manager:
-   fetching-batch-size: 5
+  fetching-batch-size: 5
+  mandant: "Landeshauptstadt Kiel"
 
diff --git a/aggregation-manager-job/src/main/resources/application.yml b/aggregation-manager-job/src/main/resources/application.yml
index 10ba491..8799164 100644
--- a/aggregation-manager-job/src/main/resources/application.yml
+++ b/aggregation-manager-job/src/main/resources/application.yml
@@ -39,6 +39,7 @@ spring:
   application:
     name: OzgCloud_AggregationManager
 
-aggregation-manager:
+ozgcloud:
   fetching-batch-size: 100
-  identifier: "id.value"
+  aggregation:
+    identifier: "id.value"
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
index a494e09..7048356 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/AggregationManagerRunnerTest.java
@@ -53,7 +53,7 @@ import de.ozgcloud.aggregation.transformation.TransformationService;
 class AggregationManagerRunnerTest {
 
 	@Mock
-	private AggregationManagerConfiguration config;
+	private AggregationManagerProperties aggregationManagerProperties;
 	@Mock
 	private TransformationProperties transformationProperties;
 	@Mock
@@ -332,7 +332,7 @@ class AggregationManagerRunnerTest {
 
 		@BeforeEach
 		void init() {
-			when(config.getFetchingBatchSize()).thenReturn(batchSize);
+			when(aggregationManagerProperties.getFetchingBatchSize()).thenReturn(batchSize);
 			when(loaderRegistry.getLoader(any())).thenReturn(loader);
 			when(aggregator.withBatchSize(anyInt())).thenReturn(aggregator);
 			when(aggregator.withLoader(any())).thenReturn(aggregator);
@@ -342,7 +342,7 @@ class AggregationManagerRunnerTest {
 		void shouldGetBatchSize() {
 			runner.createAggregator(scope);
 
-			verify(config).getFetchingBatchSize();
+			verify(aggregationManagerProperties).getFetchingBatchSize();
 		}
 
 		@Test
@@ -376,9 +376,9 @@ class AggregationManagerRunnerTest {
 
 	static class AggregationManagerRunnerImpl extends AggregationManagerRunner {
 
-		public AggregationManagerRunnerImpl(AggregationManagerConfiguration config, TransformationProperties transformationProperties,
+		public AggregationManagerRunnerImpl(AggregationManagerProperties aggregationManagerProperties, TransformationProperties transformationProperties,
 				TransformationService transformationService, AggregationDataLoaderRegistry loaderRegistry) {
-			super(config, transformationProperties, transformationService, loaderRegistry);
+			super(aggregationManagerProperties, transformationProperties, transformationService, loaderRegistry);
 		}
 
 		@Override
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
index c3711cc..8d34461 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteServiceTest.java
@@ -46,7 +46,6 @@ import org.mockito.Spy;
 import com.thedeanda.lorem.LoremIpsum;
 
 import de.ozgcloud.aggregation.Aggregation;
-import de.ozgcloud.aggregation.AggregationManagerConfiguration;
 import de.ozgcloud.aggregation.AggregationManagerProperties;
 import de.ozgcloud.aggregation.AggregationTestFactory;
 import de.ozgcloud.aggregation.data.AggregationDataServiceGrpc;
@@ -65,8 +64,6 @@ class AggregationDataRemoteServiceTest {
 	@Mock
 	private AggregationManagerProperties properties;
 	@Mock
-	private AggregationManagerConfiguration configuration;
-	@Mock
 	private GrpcAggregationDataMapper grpcAggregationDataMapper;
 	@InjectMocks
 	@Spy
@@ -124,7 +121,7 @@ class AggregationDataRemoteServiceTest {
 		@BeforeEach
 		void init() {
 			doReturn(Stream.of(GrpcAggregationDataTestFactory.create())).when(service).toGrpcAggregationDataStream(any());
-			when(configuration.getFetchingBatchSize()).thenReturn(BATCH_SIZE);
+			when(properties.getFetchingBatchSize()).thenReturn(BATCH_SIZE);
 		}
 
 		@Test
@@ -146,7 +143,7 @@ class AggregationDataRemoteServiceTest {
 		void shouldGetBatchSize() {
 			service.buildSendAggregationDataResponseObserver(AggregationTestFactory.create());
 
-			verify(configuration).getFetchingBatchSize();
+			verify(properties).getFetchingBatchSize();
 		}
 
 		@Test
diff --git a/aggregation-manager-job/src/test/resources/application-itcase.yml b/aggregation-manager-job/src/test/resources/application-itcase.yml
index b370e11..c8eef97 100644
--- a/aggregation-manager-job/src/test/resources/application-itcase.yml
+++ b/aggregation-manager-job/src/test/resources/application-itcase.yml
@@ -10,14 +10,11 @@ spring:
     banner-mode: "off"
 
 ozgcloud:
-  vorgang-manager:
-    address: static://127.0.0.1:9090
-    negotiationType: PLAINTEXT
   command:
     line:
       runner:
         enabled: false
-
-aggregation-manager:
+  mandant: "Landeshauptstadt Kiel"
   fetching-batch-size: 2
-  identifier: "id.value"
+  aggregation:
+    identifier: "id.value"
-- 
GitLab


From 3a0d2ee0b5971f60b278596afb1067d7ce8011b1 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Tue, 29 Apr 2025 17:29:04 +0200
Subject: [PATCH 12/14] OZG-7811 OZG-8114 Support mandant-value in
 helm-template

---
 .../src/main/helm/templates/cronjob.yaml      |  4 ++++
 .../src/test/helm/cronjob_env_test.yaml       | 22 +++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/aggregation-manager-job/src/main/helm/templates/cronjob.yaml b/aggregation-manager-job/src/main/helm/templates/cronjob.yaml
index f241604..e11eafe 100644
--- a/aggregation-manager-job/src/main/helm/templates/cronjob.yaml
+++ b/aggregation-manager-job/src/main/helm/templates/cronjob.yaml
@@ -75,6 +75,10 @@ spec:
               - name: ozgcloud_administration_address
                 value: {{ include "app.getOzgcloudAdministrationAddress" . }}
               {{- end }}
+              {{- if .Values.mandant }}
+              - name: ozgcloud_mandant
+                value: {{ .Values.mandant }}
+              {{- end }}
               {{- with include "app.getCustomList" . }}
 {{ . | indent 14 }}
               {{- end }}
diff --git a/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml b/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml
index 14aad4d..a7aab06 100644
--- a/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml
+++ b/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml
@@ -129,6 +129,28 @@ tests:
             name: ozgcloud_administration_address
             value: http://test-administration:8080
 
+  - it: should not set mandant per default
+    set:
+      ozgcloud:
+        environment: dev
+    asserts:
+      - notContains:
+          path: spec.jobTemplate.spec.template.spec.containers[0].env
+          content:
+            name: ozgcloud_mandant
+          any: true
+  - it: should set mandant
+    set:
+      mandant: dummy mandant value
+      ozgcloud:
+        environment: dev
+    asserts:
+      - contains:
+          path: spec.jobTemplate.spec.template.spec.containers[0].env
+          content:
+            name: ozgcloud_mandant
+            value: "dummy mandant value"
+
   - it: should fail template when env not set
     set: 
     asserts:
-- 
GitLab


From 2f8ec313a4459e2279955f471c891609f34a4157 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Wed, 30 Apr 2025 14:22:40 +0200
Subject: [PATCH 13/14] Revert "OZG-7811 OZG-8114 Support mandant-value in
 helm-template"

This reverts commit 3a0d2ee0b5971f60b278596afb1067d7ce8011b1.
---
 .../src/main/helm/templates/cronjob.yaml      |  4 ----
 .../src/test/helm/cronjob_env_test.yaml       | 22 -------------------
 2 files changed, 26 deletions(-)

diff --git a/aggregation-manager-job/src/main/helm/templates/cronjob.yaml b/aggregation-manager-job/src/main/helm/templates/cronjob.yaml
index e11eafe..f241604 100644
--- a/aggregation-manager-job/src/main/helm/templates/cronjob.yaml
+++ b/aggregation-manager-job/src/main/helm/templates/cronjob.yaml
@@ -75,10 +75,6 @@ spec:
               - name: ozgcloud_administration_address
                 value: {{ include "app.getOzgcloudAdministrationAddress" . }}
               {{- end }}
-              {{- if .Values.mandant }}
-              - name: ozgcloud_mandant
-                value: {{ .Values.mandant }}
-              {{- end }}
               {{- with include "app.getCustomList" . }}
 {{ . | indent 14 }}
               {{- end }}
diff --git a/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml b/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml
index a7aab06..14aad4d 100644
--- a/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml
+++ b/aggregation-manager-job/src/test/helm/cronjob_env_test.yaml
@@ -129,28 +129,6 @@ tests:
             name: ozgcloud_administration_address
             value: http://test-administration:8080
 
-  - it: should not set mandant per default
-    set:
-      ozgcloud:
-        environment: dev
-    asserts:
-      - notContains:
-          path: spec.jobTemplate.spec.template.spec.containers[0].env
-          content:
-            name: ozgcloud_mandant
-          any: true
-  - it: should set mandant
-    set:
-      mandant: dummy mandant value
-      ozgcloud:
-        environment: dev
-    asserts:
-      - contains:
-          path: spec.jobTemplate.spec.template.spec.containers[0].env
-          content:
-            name: ozgcloud_mandant
-            value: "dummy mandant value"
-
   - it: should fail template when env not set
     set: 
     asserts:
-- 
GitLab


From cf7b2a4adc606620b190cffc20d42e1c0e646bd0 Mon Sep 17 00:00:00 2001
From: Krzysztof <krzysztof.witukiewicz@mgm-tp.com>
Date: Wed, 30 Apr 2025 14:38:28 +0200
Subject: [PATCH 14/14] OZG-7811 OZG-8099 Rename loaders

---
 ...RemoteDataLoader.java => AggregationDataRemoteLoader.java} | 2 +-
 ...useDataLoader.java => AggregationDataWarehouseLoader.java} | 2 +-
 ...taLoaderTest.java => AggregationDataRemoteLoaderTest.java} | 4 ++--
 .../de/ozgcloud/aggregation/extern/SpringContextITCase.java   | 2 +-
 ...oaderTest.java => AggregationDataWarehouseLoaderTest.java} | 4 ++--
 .../ozgcloud/aggregation/warehouse/SpringContextITCase.java   | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)
 rename aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/{AggregationRemoteDataLoader.java => AggregationDataRemoteLoader.java} (97%)
 rename aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/{AggregationWarehouseDataLoader.java => AggregationDataWarehouseLoader.java} (96%)
 rename aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/{AggregationRemoteDataLoaderTest.java => AggregationDataRemoteLoaderTest.java} (97%)
 rename aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/{AggregationWarehouseDataLoaderTest.java => AggregationDataWarehouseLoaderTest.java} (97%)

diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteLoader.java
similarity index 97%
rename from aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
rename to aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteLoader.java
index 845c203..81701ad 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteLoader.java
@@ -39,7 +39,7 @@ import lombok.extern.log4j.Log4j2;
 @ConditionalOnProperty("grpc.client.aggregation-manager.address")
 @RequiredArgsConstructor
 @Log4j2
-public class AggregationRemoteDataLoader implements AggregationDataLoader {
+public class AggregationDataRemoteLoader implements AggregationDataLoader {
 
 	private final AggregationDataRemoteService service;
 
diff --git a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoader.java b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationDataWarehouseLoader.java
similarity index 96%
rename from aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoader.java
rename to aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationDataWarehouseLoader.java
index 3e319bf..3ef137a 100644
--- a/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoader.java
+++ b/aggregation-manager-job/src/main/java/de/ozgcloud/aggregation/warehouse/AggregationDataWarehouseLoader.java
@@ -35,7 +35,7 @@ import lombok.extern.log4j.Log4j2;
 @Component
 @RequiredArgsConstructor
 @Log4j2
-public class AggregationWarehouseDataLoader implements AggregationDataLoader {
+public class AggregationDataWarehouseLoader implements AggregationDataLoader {
 
 	private final WarehouseRepository repository;
 
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteLoaderTest.java
similarity index 97%
rename from aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
rename to aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteLoaderTest.java
index 045febe..3c7b0b0 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationRemoteDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/AggregationDataRemoteLoaderTest.java
@@ -44,13 +44,13 @@ import de.ozgcloud.aggregation.transformation.AggregationMapping;
 import de.ozgcloud.common.errorhandling.TechnicalException;
 import lombok.SneakyThrows;
 
-class AggregationRemoteDataLoaderTest {
+class AggregationDataRemoteLoaderTest {
 
 	@Mock
 	private AggregationDataRemoteService remoteService;
 	@Spy
 	@InjectMocks
-	private AggregationRemoteDataLoader loader;
+	private AggregationDataRemoteLoader loader;
 
 	@Nested
 	class TestLoadIntoTarget {
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java
index 2c7e93e..b5cd918 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/extern/SpringContextITCase.java
@@ -51,7 +51,7 @@ class SpringContextITCase {
 
 		@Test
 		void shouldHaveOneLoader() {
-			assertThat(getLoadersWithScopeExtern(loaders)).singleElement().isInstanceOf(AggregationRemoteDataLoader.class);
+			assertThat(getLoadersWithScopeExtern(loaders)).singleElement().isInstanceOf(AggregationDataRemoteLoader.class);
 		}
 
 		@Test
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoaderTest.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationDataWarehouseLoaderTest.java
similarity index 97%
rename from aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoaderTest.java
rename to aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationDataWarehouseLoaderTest.java
index ed86396..1348836 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationWarehouseDataLoaderTest.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/AggregationDataWarehouseLoaderTest.java
@@ -45,13 +45,13 @@ import de.ozgcloud.aggregation.Aggregation;
 import de.ozgcloud.aggregation.AggregationTestFactory;
 import de.ozgcloud.aggregation.transformation.AggregationMapping;
 
-class AggregationWarehouseDataLoaderTest {
+class AggregationDataWarehouseLoaderTest {
 
 	@Mock
 	private WarehouseRepository repository;
 	@Spy
 	@InjectMocks
-	private AggregationWarehouseDataLoader loader;
+	private AggregationDataWarehouseLoader loader;
 
 	@Nested
 	class TestLoadIntoTarget {
diff --git a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java
index ed88f4f..5504e78 100644
--- a/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java
+++ b/aggregation-manager-job/src/test/java/de/ozgcloud/aggregation/warehouse/SpringContextITCase.java
@@ -45,7 +45,7 @@ class SpringContextITCase {
 
 	@Test
 	void shouldHaveLoader() {
-		assertThat(getLoadersWithScopeIntern(loaders)).singleElement().isInstanceOf(AggregationWarehouseDataLoader.class);
+		assertThat(getLoadersWithScopeIntern(loaders)).singleElement().isInstanceOf(AggregationDataWarehouseLoader.class);
 	}
 
 	@Test
-- 
GitLab