Skip to content

Commit e30464c

Browse files
authored
Introduce sample batch domain model (#212)
Adds implementation and support for sample registration and batch registration. Also includes a new domain dispatcher implementation backed by JobRunr, that allows for persistent event management. # Co-authored-by: KochTobi <[email protected]>
1 parent 8a7e8a4 commit e30464c

File tree

28 files changed

+1177
-0
lines changed

28 files changed

+1177
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package life.qbic.projectmanagement.experiment.persistence;
2+
3+
import static life.qbic.logging.service.LoggerFactory.logger;
4+
5+
import java.util.Optional;
6+
import life.qbic.application.commons.Result;
7+
import life.qbic.logging.api.Logger;
8+
import life.qbic.projectmanagement.domain.project.repository.BatchRepository;
9+
import life.qbic.projectmanagement.domain.project.sample.Batch;
10+
import life.qbic.projectmanagement.domain.project.sample.BatchId;
11+
import life.qbic.projectmanagement.domain.project.service.BatchDomainService.ResponseCode;
12+
import org.springframework.beans.factory.annotation.Autowired;
13+
import org.springframework.stereotype.Repository;
14+
15+
/**
16+
* <b>Batch JPA Repository</b>
17+
*
18+
* <p>Implementation of the {@link BatchRepository} interface.</p>
19+
*
20+
* @since 1.0.0
21+
*/
22+
@Repository
23+
public class BatchJpaRepository implements BatchRepository {
24+
25+
private static final Logger log = logger(BatchJpaRepository.class);
26+
27+
private final QbicBatchRepo qbicBatchRepo;
28+
29+
@Autowired
30+
public BatchJpaRepository(QbicBatchRepo qbicBatchRepo) {
31+
this.qbicBatchRepo = qbicBatchRepo;
32+
}
33+
34+
@Override
35+
public Result<Batch, ResponseCode> add(Batch batch) {
36+
try {
37+
qbicBatchRepo.save(batch);
38+
} catch (Exception e) {
39+
log.error(e.getMessage(), e);
40+
return Result.fromError(ResponseCode.BATCH_REGISTRATION_FAILED);
41+
}
42+
return Result.fromValue(batch);
43+
}
44+
45+
@Override
46+
public Optional<Batch> find(BatchId batchId) {
47+
return this.qbicBatchRepo.findById(batchId);
48+
}
49+
50+
@Override
51+
public Result<Batch, ResponseCode> update(Batch batch) {
52+
return Result.fromValue(this.qbicBatchRepo.save(batch));
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package life.qbic.projectmanagement.experiment.persistence;
2+
3+
import life.qbic.projectmanagement.domain.project.sample.Batch;
4+
import life.qbic.projectmanagement.domain.project.sample.BatchId;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
public interface QbicBatchRepo extends JpaRepository<Batch, BatchId> {
8+
9+
10+
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package life.qbic.projectmanagement.experiment.persistence;
2+
3+
import life.qbic.projectmanagement.domain.project.sample.Sample;
4+
import life.qbic.projectmanagement.domain.project.sample.SampleId;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
public interface QbicSampleRepository extends JpaRepository<Sample, SampleId> {
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package life.qbic.projectmanagement.experiment.persistence;
2+
3+
import static life.qbic.logging.service.LoggerFactory.logger;
4+
5+
import life.qbic.application.commons.Result;
6+
import life.qbic.logging.api.Logger;
7+
import life.qbic.projectmanagement.domain.project.repository.SampleRepository;
8+
import life.qbic.projectmanagement.domain.project.sample.Sample;
9+
import life.qbic.projectmanagement.domain.project.service.SampleDomainService.ResponseCode;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.stereotype.Repository;
12+
13+
/**
14+
* <b>Sample JPA Repository</b>
15+
*
16+
* <p>Implementation of the {@link SampleRepository} interface</p>
17+
*
18+
* @since 1.0.0
19+
*/
20+
@Repository
21+
public class SampleJpaRepository implements SampleRepository {
22+
23+
private static final Logger log = logger(SampleJpaRepository.class);
24+
25+
private final QbicSampleRepository qbicSampleRepository;
26+
27+
@Autowired
28+
public SampleJpaRepository(QbicSampleRepository qbicSampleRepository) {
29+
this.qbicSampleRepository = qbicSampleRepository;
30+
}
31+
32+
@Override
33+
public Result<Sample, ResponseCode> add(Sample sample) {
34+
try {
35+
this.qbicSampleRepository.save(sample);
36+
} catch (Exception e) {
37+
log.error("Saving sample with id failed:" + sample.sampleId(), e);
38+
return Result.fromError(ResponseCode.REGISTRATION_FAILED);
39+
}
40+
return Result.fromValue(sample);
41+
}
42+
}

pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@
131131
<version>2.4-M1-groovy-4.0</version>
132132
<scope>test</scope>
133133
</dependency>
134+
<dependency>
135+
<groupId>org.spockframework</groupId>
136+
<artifactId>spock-spring</artifactId>
137+
<version>2.4-M1-groovy-4.0</version>
138+
<scope>test</scope>
139+
</dependency>
134140
<dependency>
135141
<groupId>org.apache.groovy</groupId>
136142
<artifactId>groovy-all</artifactId>

projectmanagement/pom.xml

+29
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@
2020
<artifactId>spock-core</artifactId>
2121
<scope>test</scope>
2222
</dependency>
23+
<dependency>
24+
<groupId>org.springframework.boot</groupId>
25+
<artifactId>spring-boot</artifactId>
26+
<scope>test</scope>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-test</artifactId>
31+
<scope>test</scope>
32+
</dependency>
2333
<dependency>
2434
<groupId>life.qbic</groupId>
2535
<artifactId>finances</artifactId>
@@ -48,5 +58,24 @@
4858
<version>6.0.2</version>
4959
<scope>compile</scope>
5060
</dependency>
61+
<dependency>
62+
<groupId>life.qbic</groupId>
63+
<artifactId>domain-concept</artifactId>
64+
<version>0.15.1</version>
65+
<scope>compile</scope>
66+
</dependency>
67+
<dependency>
68+
<groupId>com.fasterxml.jackson.core</groupId>
69+
<artifactId>jackson-databind</artifactId>
70+
</dependency>
71+
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 -->
72+
<dependency>
73+
<groupId>com.fasterxml.jackson.datatype</groupId>
74+
<artifactId>jackson-datatype-jsr310</artifactId>
75+
</dependency>
76+
<dependency>
77+
<groupId>org.jobrunr</groupId>
78+
<artifactId>jobrunr-spring-boot-3-starter</artifactId>
79+
</dependency>
5180
</dependencies>
5281
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package life.qbic.projectmanagement.application.batch;
2+
3+
import life.qbic.application.commons.Result;
4+
import life.qbic.projectmanagement.domain.project.repository.BatchRepository;
5+
import life.qbic.projectmanagement.domain.project.sample.Batch;
6+
import life.qbic.projectmanagement.domain.project.sample.BatchId;
7+
import life.qbic.projectmanagement.domain.project.sample.SampleId;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.stereotype.Service;
10+
11+
/**
12+
* <b><class short description - 1 Line!></b>
13+
*
14+
* <p><More detailed description - When to use, what it solves, etc.></p>
15+
*
16+
* @since <version tag>
17+
*/
18+
@Service
19+
public class BatchRegistrationService {
20+
21+
private final BatchRepository batchRepository;
22+
23+
public BatchRegistrationService(@Autowired BatchRepository batchRepository) {
24+
this.batchRepository = batchRepository;
25+
}
26+
27+
public Result<BatchId, ResponseCode> registerBatch(String label, boolean isPilot) {
28+
Batch batch = Batch.create(label, isPilot);
29+
var result = batchRepository.add(batch);
30+
if (result.isError()) {
31+
return Result.fromError(ResponseCode.BATCH_CREATION_FAILED);
32+
}
33+
return Result.fromValue(batch.batchId());
34+
}
35+
36+
public Result<BatchId, ResponseCode> addSampleToBatch(SampleId sampleId, BatchId batchId) {
37+
var searchResult = batchRepository.find(batchId);
38+
if (searchResult.isEmpty()) {
39+
return Result.fromError(ResponseCode.BATCH_NOT_FOUND);
40+
}
41+
searchResult.ifPresent(batch -> {
42+
batch.addSample(sampleId);
43+
batchRepository.update(batch);
44+
});
45+
return Result.fromValue(batchId);
46+
}
47+
48+
public enum ResponseCode {
49+
BATCH_UPDATE_FAILED, BATCH_NOT_FOUND, BATCH_CREATION_FAILED
50+
}
51+
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package life.qbic.projectmanagement.application.policy;
2+
3+
import life.qbic.domain.concepts.DomainEventDispatcher;
4+
import life.qbic.projectmanagement.application.policy.directive.AddSampleToBatch;
5+
6+
/**
7+
* <b>Policy: Sample Registered</b>
8+
* <p>
9+
* A collection of all directives that need to be executed after a new sample has been registered
10+
* for measurement.
11+
* <p>
12+
* The policy subscribes to events of type
13+
* {@link life.qbic.projectmanagement.domain.project.sample.event.SampleRegistered} and ensures the
14+
* registration of all business required directives.
15+
*
16+
* @since 1.0.0
17+
*/
18+
public class SampleRegisteredPolicy {
19+
20+
private final AddSampleToBatch addSampleToBatch;
21+
22+
/**
23+
* Creates an instance of a {@link SampleRegisteredPolicy} object.
24+
* <p>
25+
* All directives will be created an subscribed upon instantiation.
26+
*
27+
* @param addSampleToBatch directive to update the affected sample
28+
* {@link life.qbic.projectmanagement.domain.project.sample.Batch}
29+
* @since 1.0.0
30+
*/
31+
public SampleRegisteredPolicy(AddSampleToBatch addSampleToBatch) {
32+
this.addSampleToBatch = addSampleToBatch;
33+
DomainEventDispatcher.instance().subscribe(this.addSampleToBatch);
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package life.qbic.projectmanagement.application.policy.directive;
2+
3+
import life.qbic.domain.concepts.DomainEvent;
4+
import life.qbic.domain.concepts.DomainEventSubscriber;
5+
import life.qbic.projectmanagement.application.batch.BatchRegistrationService;
6+
import life.qbic.projectmanagement.domain.project.sample.event.SampleRegistered;
7+
import org.jobrunr.scheduling.JobScheduler;
8+
9+
/**
10+
* <b>Directive: Add Sample to Batch</b>
11+
* <p>
12+
* After a sample has been registered and assigned to a batch, we need to update the batch and add
13+
* the sample reference of the newly registered sample.
14+
*
15+
* @since 1.0.0
16+
*/
17+
public class AddSampleToBatch implements DomainEventSubscriber<SampleRegistered> {
18+
19+
private final BatchRegistrationService batchRegistrationService;
20+
21+
private final JobScheduler jobScheduler;
22+
23+
public AddSampleToBatch(BatchRegistrationService batchRegistrationService,
24+
JobScheduler jobScheduler) {
25+
this.batchRegistrationService = batchRegistrationService;
26+
this.jobScheduler = jobScheduler;
27+
}
28+
29+
@Override
30+
public Class<? extends DomainEvent> subscribedToEventType() {
31+
return SampleRegistered.class;
32+
}
33+
34+
@Override
35+
public void handleEvent(SampleRegistered event) {
36+
jobScheduler.enqueue(() -> batchRegistrationService.addSampleToBatch(event.registeredSample(),
37+
event.assignedBatch()));
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package life.qbic.projectmanagement.domain.project.repository;
2+
3+
import java.util.Optional;
4+
import life.qbic.application.commons.Result;
5+
import life.qbic.projectmanagement.domain.project.sample.Batch;
6+
import life.qbic.projectmanagement.domain.project.sample.BatchId;
7+
import life.qbic.projectmanagement.domain.project.service.BatchDomainService.ResponseCode;
8+
9+
/**
10+
* Batch data storage interface
11+
* <p>
12+
* Provides access to the persistence layer that handles the {@link Batch} data storage.
13+
*
14+
* @since 1.0.0
15+
*/
16+
public interface BatchRepository {
17+
18+
/**
19+
* Saves a {@link Batch} entity persistently.
20+
*
21+
* @param batch the sample batch to register
22+
* @return a {@link Result} with the batch as value or an error response code {@link ResponseCode}.
23+
* @since 1.0.0
24+
*/
25+
Result<Batch, ResponseCode> add(Batch batch);
26+
27+
Optional<Batch> find(BatchId batchId);
28+
29+
Result<Batch, ResponseCode> update(Batch batch);
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package life.qbic.projectmanagement.domain.project.repository;
2+
3+
import life.qbic.application.commons.Result;
4+
import life.qbic.projectmanagement.domain.project.sample.Sample;
5+
import life.qbic.projectmanagement.domain.project.service.SampleDomainService.ResponseCode;
6+
7+
/**
8+
* Sample data storage interface
9+
* <p>
10+
* Provides access to the persistence layer that handles {@link Sample} data storage.
11+
*
12+
* @since 1.0.0
13+
*/
14+
public interface SampleRepository {
15+
16+
/**
17+
* Saves a sample entity persistently.
18+
*
19+
* @param sample the sample to save.
20+
* @since 1.0.0
21+
*/
22+
Result<Sample, ResponseCode> add(Sample sample);
23+
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package life.qbic.projectmanagement.domain.project.repository.jpa;
2+
3+
import jakarta.persistence.AttributeConverter;
4+
import life.qbic.projectmanagement.domain.project.sample.BatchId;
5+
6+
/**
7+
* <b><class short description - 1 Line!></b>
8+
*
9+
* <p><More detailed description - When to use, what it solves, etc.></p>
10+
*
11+
* @since <version tag>
12+
*/
13+
public class BatchIdConverter implements AttributeConverter<BatchId, String> {
14+
15+
@Override
16+
public String convertToDatabaseColumn(BatchId batchId) {
17+
return batchId.value();
18+
}
19+
20+
@Override
21+
public BatchId convertToEntityAttribute(String s) {
22+
return BatchId.parse(s);
23+
}
24+
}

0 commit comments

Comments
 (0)