Skip to content

Commit aed6553

Browse files
authored
Refactor Backend Architecture for the AasRepository and AasService (#570)
* test: extract test suite from aasmanagermultithreading test * test: add failing test for aasenv multithreading mongoDB * refactor: experimental change of the design of the AasBackend + CrudAasRepository * refactor: replace AasService dep in AasRespository through Repository Fragment * feat: implement SmRef methods of the MongoDB AasServiceBackend * style: add license header * refactor: introduce method to inject a generic fragment -- indep. of backend * feat: implement InMemoryAasBackend based on AasService * refactor: rename AasServiceBackend to AasServiceOperations * refactor: rename AasBackend to AasRepositoryBackend * test: fix failing tests due to InMemoryAasBackend change * refactor: improve name consistency * refactor: rename SimpleAasRepositoryFactory to CrudAasRepositoryFactory * refactor: modify CrudAasRepositoryFactory constructor * feat: implement remaining methods from MongoDBAasServiceOperations * fix: fix exception handling in MongoDbAasServiceOperations * fix: remaining tests affected by the arch refactor * fix: add configuration to registryintegration test * test: limit configuration to registryintegration tests * refactor: move AasServiceOperation to aasservice * feat: add repository fragment configuration postProcessor * refactor: remove qualifier from aasServiceOperations bean * fix: fix InMemory backend not working * refactor: move MongoDBAasServiceOperations to aasservice-backend-mongodb * docs: improve brief of AasRepositoryFragmentConfig * refactor: extract filehandling logic from service to util * optimize: reduce number of queries when using aasServiceOperations by improving interface expectations * refactor: simplify MongoDBAasServiceOps collectionName injection * docs: bump license header year; add missing license headers
1 parent 7318211 commit aed6553

File tree

43 files changed

+1227
-501
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1227
-501
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*******************************************************************************
2+
* Copyright (C) 2025 the Eclipse BaSyx Authors
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining
5+
* a copy of this software and associated documentation files (the
6+
* "Software"), to deal in the Software without restriction, including
7+
* without limitation the rights to use, copy, modify, merge, publish,
8+
* distribute, sublicense, and/or sell copies of the Software, and to
9+
* permit persons to whom the Software is furnished to do so, subject to
10+
* the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be
13+
* included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*
23+
* SPDX-License-Identifier: MIT
24+
******************************************************************************/
25+
26+
package org.eclipse.digitaltwin.basyx.aasenvironment.client;
27+
28+
import static org.junit.Assert.*;
29+
30+
import java.util.Collection;
31+
import java.util.List;
32+
import java.util.UUID;
33+
import java.util.concurrent.ConcurrentLinkedDeque;
34+
import java.util.concurrent.ExecutionException;
35+
import java.util.concurrent.ExecutorService;
36+
import java.util.concurrent.Executors;
37+
import java.util.concurrent.Future;
38+
import java.util.stream.IntStream;
39+
40+
import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell;
41+
import org.eclipse.digitaltwin.aas4j.v3.model.Key;
42+
import org.eclipse.digitaltwin.aas4j.v3.model.Reference;
43+
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell;
44+
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel;
45+
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
46+
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
47+
import org.junit.Test;
48+
49+
public abstract class ConnectedAasManagerMultithreadingTestSuite {
50+
static final int N_THREADS = 20;
51+
52+
protected abstract AasRepository getAasRepository();
53+
54+
protected abstract ConnectedAasManager getConnectedAasManager();
55+
56+
@Test
57+
public void testParallelSubmodelCreation() throws ExecutionException, InterruptedException {
58+
AssetAdministrationShell shell = createShell();
59+
60+
ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS);
61+
ConcurrentLinkedDeque<String> createdSubmodelIds = new ConcurrentLinkedDeque<>();
62+
63+
List<Future<Boolean>> futures = IntStream.range(0, N_THREADS).mapToObj(i -> executorService.submit(() -> createdSubmodelIds.add(createSubmodel(shell.getId(), i)))).toList();
64+
65+
try {
66+
for (int i = 0; i < N_THREADS; i++) {
67+
futures.get(i).get();
68+
}
69+
} finally {
70+
executorService.shutdown();
71+
}
72+
73+
createdSubmodelIds.forEach(submodelId -> assertSubmodelWasCreatedAndRegistered(shell.getId(), submodelId));
74+
}
75+
76+
void assertSubmodelWasCreatedAndRegistered(String shellId, String submodelId) {
77+
assertEquals("No submodel with id " + submodelId + " found by the client", submodelId, getConnectedAasManager().getSubmodelService(submodelId).getSubmodel().getId());
78+
assertTrue("SubmodelRef " + submodelId + " not found in shell " + shellId,
79+
getAasRepository().getSubmodelReferences(shellId, PaginationInfo.NO_LIMIT).getResult().stream().map(Reference::getKeys).flatMap(Collection::stream).map(Key::getValue).anyMatch(submodelId::equals));
80+
}
81+
82+
private AssetAdministrationShell createShell() {
83+
String id = UUID.randomUUID().toString();
84+
DefaultAssetAdministrationShell shell = new DefaultAssetAdministrationShell.Builder().id(id).build();
85+
getConnectedAasManager().createAas(shell);
86+
return getConnectedAasManager().getAasService(id).getAAS();
87+
}
88+
89+
private String createSubmodel(String aasId, int threadId) {
90+
try {
91+
String id = aasId + "-thread" + threadId;
92+
DefaultSubmodel submodel = new DefaultSubmodel.Builder().id(id).build();
93+
getConnectedAasManager().createSubmodelInAas(aasId, submodel);
94+
return id;
95+
} catch (Exception e) {
96+
throw new RuntimeException("Failed at thread " + threadId, e);
97+
}
98+
}
99+
100+
}

basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreading.java renamed to basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreadingInMemory.java

Lines changed: 10 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (C) 2024 the Eclipse BaSyx Authors
2+
* Copyright (C) 2025 the Eclipse BaSyx Authors
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining
55
* a copy of this software and associated documentation files (the
@@ -25,60 +25,33 @@
2525

2626
package org.eclipse.digitaltwin.basyx.aasenvironment.client;
2727

28-
import static org.junit.Assert.assertEquals;
29-
import static org.junit.Assert.assertTrue;
30-
31-
import java.util.Collection;
32-
import java.util.List;
33-
import java.util.UUID;
34-
import java.util.concurrent.ConcurrentLinkedDeque;
35-
import java.util.concurrent.ExecutionException;
36-
import java.util.concurrent.ExecutorService;
37-
import java.util.concurrent.Executors;
38-
import java.util.concurrent.Future;
39-
import java.util.stream.IntStream;
40-
41-
import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell;
42-
import org.eclipse.digitaltwin.aas4j.v3.model.Key;
43-
import org.eclipse.digitaltwin.aas4j.v3.model.Reference;
44-
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell;
45-
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel;
4628
import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi;
4729
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
4830
import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository;
49-
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
5031
import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi;
51-
import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository;
5232
import org.eclipse.digitaltwin.basyx.submodelrepository.client.ConnectedSubmodelRepository;
5333
import org.junit.After;
5434
import org.junit.AfterClass;
5535
import org.junit.BeforeClass;
56-
import org.junit.Test;
5736
import org.springframework.boot.SpringApplication;
5837
import org.springframework.context.ConfigurableApplicationContext;
5938

60-
public class TestConnectedAasManagerMultithreading {
39+
public class TestConnectedAasManagerMultithreadingInMemory extends ConnectedAasManagerMultithreadingTestSuite {
6140
static final String AAS_REPOSITORY_BASE_PATH = "http://localhost:8081";
6241
static final String SM_REPOSITORY_BASE_PATH = "http://localhost:8081";
6342
static final String AAS_REGISTRY_BASE_PATH = "http://localhost:8050";
6443
static final String SM_REGISTRY_BASE_PATH = "http://localhost:8060";
65-
static final int N_THREADS = 20;
66-
67-
static ConfigurableApplicationContext appContext;
68-
static AasRepository aasRepository;
69-
static SubmodelRepository smRepository;
7044

45+
static ConnectedAasManager aasManager;
7146
static ConnectedAasRepository connectedAasRepository;
7247
static ConnectedSubmodelRepository connectedSmRepository;
7348
static RegistryAndDiscoveryInterfaceApi aasRegistryApi;
7449
static SubmodelRegistryApi smRegistryApi;
75-
76-
static ConnectedAasManager aasManager;
50+
static ConfigurableApplicationContext appContext;
7751

7852
@BeforeClass
7953
public static void setupRepositories() {
8054
appContext = new SpringApplication(DummyAasEnvironmentComponent.class).run(new String[] {});
81-
8255
connectedAasRepository = new ConnectedAasRepository(AAS_REPOSITORY_BASE_PATH);
8356
connectedSmRepository = new ConnectedSubmodelRepository(SM_REPOSITORY_BASE_PATH);
8457
aasRegistryApi = new RegistryAndDiscoveryInterfaceApi(AAS_REGISTRY_BASE_PATH);
@@ -98,32 +71,6 @@ public static void stopContext() {
9871
appContext.close();
9972
}
10073

101-
@Test
102-
public void testParallelSubmodelCreation() throws ExecutionException, InterruptedException {
103-
AssetAdministrationShell shell = createShell();
104-
105-
ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS);
106-
ConcurrentLinkedDeque<String> createdSubmodelIds = new ConcurrentLinkedDeque<>();
107-
108-
List<Future<Boolean>> futures = IntStream.range(0, N_THREADS).mapToObj(i -> executorService.submit(() -> createdSubmodelIds.add(createSubmodel(shell.getId(), i)))).toList();
109-
110-
try {
111-
for (int i = 0; i < N_THREADS; i++) {
112-
futures.get(i).get();
113-
}
114-
} finally {
115-
executorService.shutdown();
116-
}
117-
118-
createdSubmodelIds.forEach(submodelId -> assertSubmodelWasCreatedAndRegistered(shell.getId(), submodelId));
119-
}
120-
121-
static void assertSubmodelWasCreatedAndRegistered(String shellId, String submodelId) {
122-
assertEquals(submodelId, aasManager.getSubmodelService(submodelId).getSubmodel().getId());
123-
assertTrue(connectedAasRepository.getSubmodelReferences(shellId, PaginationInfo.NO_LIMIT).getResult().stream().map(Reference::getKeys).flatMap(Collection::stream).map(Key::getValue).anyMatch(submodelId::equals));
124-
}
125-
126-
12774
private static void cleanUpRegistries() {
12875
try {
12976
aasRegistryApi.deleteAllShellDescriptors();
@@ -137,22 +84,13 @@ private static void cleanUpRegistries() {
13784
}
13885
}
13986

140-
private static AssetAdministrationShell createShell() {
141-
String id = UUID.randomUUID().toString();
142-
DefaultAssetAdministrationShell shell = new DefaultAssetAdministrationShell.Builder().id(id).build();
143-
aasManager.createAas(shell);
144-
return aasManager.getAasService(id).getAAS();
87+
@Override
88+
public AasRepository getAasRepository() {
89+
return connectedAasRepository;
14590
}
14691

147-
private static String createSubmodel(String aasId, int threadId) {
148-
try {
149-
String id = aasId + "-thread" + threadId;
150-
DefaultSubmodel submodel = new DefaultSubmodel.Builder().id(id).build();
151-
aasManager.createSubmodelInAas(aasId, submodel);
152-
return id;
153-
} catch (Exception e) {
154-
throw new RuntimeException("Failed at thread " + threadId, e);
155-
}
92+
@Override
93+
public ConnectedAasManager getConnectedAasManager() {
94+
return aasManager;
15695
}
157-
15896
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*******************************************************************************
2+
* Copyright (C) 2025 the Eclipse BaSyx Authors
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining
5+
* a copy of this software and associated documentation files (the
6+
* "Software"), to deal in the Software without restriction, including
7+
* without limitation the rights to use, copy, modify, merge, publish,
8+
* distribute, sublicense, and/or sell copies of the Software, and to
9+
* permit persons to whom the Software is furnished to do so, subject to
10+
* the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be
13+
* included in all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19+
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21+
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*
23+
* SPDX-License-Identifier: MIT
24+
******************************************************************************/
25+
26+
package org.eclipse.digitaltwin.basyx.aasenvironment.client;
27+
28+
import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi;
29+
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
30+
import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository;
31+
import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi;
32+
import org.eclipse.digitaltwin.basyx.submodelrepository.client.ConnectedSubmodelRepository;
33+
import org.junit.After;
34+
import org.junit.AfterClass;
35+
import org.junit.BeforeClass;
36+
import org.springframework.boot.builder.SpringApplicationBuilder;
37+
import org.springframework.context.ConfigurableApplicationContext;
38+
39+
public class TestConnectedAasManagerMultithreadingMongoDb extends ConnectedAasManagerMultithreadingTestSuite {
40+
static final String AAS_REPOSITORY_BASE_PATH = "http://localhost:8081";
41+
static final String SM_REPOSITORY_BASE_PATH = "http://localhost:8081";
42+
static final String AAS_REGISTRY_BASE_PATH = "http://localhost:8050";
43+
static final String SM_REGISTRY_BASE_PATH = "http://localhost:8060";
44+
45+
static ConnectedAasManager aasManager;
46+
static ConnectedAasRepository connectedAasRepository;
47+
static ConnectedSubmodelRepository connectedSmRepository;
48+
static RegistryAndDiscoveryInterfaceApi aasRegistryApi;
49+
static SubmodelRegistryApi smRegistryApi;
50+
static ConfigurableApplicationContext appContext;
51+
52+
@BeforeClass
53+
public static void setupRepositories() {
54+
appContext = new SpringApplicationBuilder(DummyAasEnvironmentComponent.class).profiles("mongodb").run(new String[] {});
55+
connectedAasRepository = new ConnectedAasRepository(AAS_REPOSITORY_BASE_PATH);
56+
connectedSmRepository = new ConnectedSubmodelRepository(SM_REPOSITORY_BASE_PATH);
57+
aasRegistryApi = new RegistryAndDiscoveryInterfaceApi(AAS_REGISTRY_BASE_PATH);
58+
smRegistryApi = new SubmodelRegistryApi(SM_REGISTRY_BASE_PATH);
59+
aasManager = new ConnectedAasManager(AAS_REGISTRY_BASE_PATH, AAS_REPOSITORY_BASE_PATH, SM_REGISTRY_BASE_PATH, SM_REPOSITORY_BASE_PATH);
60+
61+
cleanUpRegistries();
62+
}
63+
64+
@After
65+
public void cleanUpComponents() {
66+
cleanUpRegistries();
67+
}
68+
69+
@AfterClass
70+
public static void stopContext() {
71+
appContext.close();
72+
}
73+
74+
private static void cleanUpRegistries() {
75+
try {
76+
aasRegistryApi.deleteAllShellDescriptors();
77+
} catch (Exception e) {
78+
System.out.println(e.getMessage());
79+
}
80+
try {
81+
smRegistryApi.deleteAllSubmodelDescriptors();
82+
} catch (Exception e) {
83+
System.out.println(e.getMessage());
84+
}
85+
}
86+
87+
@Override
88+
public AasRepository getAasRepository() {
89+
return connectedAasRepository;
90+
}
91+
92+
@Override
93+
public ConnectedAasManager getConnectedAasManager() {
94+
return aasManager;
95+
}
96+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
basyx.backend = MongoDB
2+
3+
spring.data.mongodb.host=127.0.0.1
4+
spring.data.mongodb.port=27017
5+
spring.data.mongodb.database=aas-env
6+
spring.data.mongodb.authentication-database=admin
7+
spring.data.mongodb.username=mongoAdmin
8+
spring.data.mongodb.password=mongoPassword

basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (C) 2024 the Eclipse BaSyx Authors
2+
* Copyright (C) 2025 the Eclipse BaSyx Authors
33
*
44
* Permission is hereby granted, free of charge, to any person obtaining
55
* a copy of this software and associated documentation files (the
@@ -36,8 +36,7 @@
3636
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
3737
import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepository;
3838
import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudConceptDescriptionRepository;
39-
import org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory.AasInMemoryBackendProvider;
40-
import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasServiceFactory;
39+
import org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory.InMemoryAasRepositoryBackend;
4140
import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionInMemoryBackendProvider;
4241
import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository;
4342
import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException;
@@ -77,7 +76,7 @@ public class AasEnvironmentLoaderTest {
7776
@Before
7877
public void setUp() {
7978
submodelRepository = Mockito.spy(new CrudSubmodelRepository(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())));
80-
aasRepository = Mockito.spy(new CrudAasRepository(new AasInMemoryBackendProvider(), new InMemoryAasServiceFactory(new InMemoryFileRepository())));
79+
aasRepository = Mockito.spy(new CrudAasRepository(InMemoryAasRepositoryBackend.buildDefault(), "aas-repo"));
8180
conceptDescriptionRepository = Mockito.spy(new CrudConceptDescriptionRepository(new ConceptDescriptionInMemoryBackendProvider()));
8281
}
8382

0 commit comments

Comments
 (0)