Skip to content

Commit

Permalink
GROUP-18 Added a Data Loader Job to automatically add active groups a…
Browse files Browse the repository at this point in the history
…nd auto-disband expired groups.
  • Loading branch information
makmn1 committed Sep 5, 2023
1 parent ee00059 commit c5059aa
Show file tree
Hide file tree
Showing 15 changed files with 519 additions and 42 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ dependencies {
// implementation "org.springframework.cloud:spring-cloud-starter-config"
implementation "org.springframework.boot:spring-boot-starter-security:3.1.3"

implementation 'com.github.javafaker:javafaker:1.0.2'
implementation "io.sentry:sentry-spring-boot-starter-jakarta:6.28.0"

runtimeOnly 'org.flywaydb:flyway-core'
Expand All @@ -81,6 +82,7 @@ dependencies {
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:postgresql'
testImplementation 'org.testcontainers:r2dbc'
testImplementation 'org.awaitility:awaitility:4.2.0'

// Dependencies needed for Cucumber
testImplementation(platform("org.junit:junit-bom:5.10.0"))
Expand Down
6 changes: 5 additions & 1 deletion config/pmd/codestyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
<property name="minimum" value="2" />
</properties>
</rule>
<rule ref="category/java/codestyle.xml/TooManyStaticImports" />
<rule ref="category/java/codestyle.xml/TooManyStaticImports">
<properties>
<property name="maximumStaticImports" value="5" />
</properties>
</rule>
<rule ref="category/java/codestyle.xml/UnnecessaryAnnotationValueElement" />
<rule ref="category/java/codestyle.xml/UnnecessaryCast" />
<rule ref="category/java/codestyle.xml/UnnecessaryConstructor" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
* The entry point to the application setting up the Spring Context.
*/
@SpringBootApplication
@EnableScheduling
@ConfigurationPropertiesScan
public class GroupServiceApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.grouphq.groupservice.group.demo;

import com.grouphq.groupservice.group.domain.groups.Group;
import com.grouphq.groupservice.group.domain.groups.GroupRepository;
import com.grouphq.groupservice.group.domain.groups.GroupService;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
* A Spring Job scheduler for periodically adding active groups
* and auto-disbanding expired groups.
*/
@Component
public class GroupDemoLoader {

private boolean initialStateLoaded;

private final int initialGroupSize;

private final int periodicGroupAdditionCount;

private final GroupRepository groupRepository;

private final GroupService groupService;

/**
* Gathers dependencies and values needed for demo loader.
*/
public GroupDemoLoader(GroupService groupService,

GroupRepository groupRepository,

@Value("${group.loader.initial-group-size}")
int initialGroupSize,

@Value("${group.loader.periodic-group-addition-count}")
int periodicGroupAdditionCount
) {
this.groupService = groupService;
this.groupRepository = groupRepository;

this.initialGroupSize = initialGroupSize;
this.periodicGroupAdditionCount = periodicGroupAdditionCount;
}

@Scheduled(initialDelayString = "${group.loader.initial-group-delay}",
fixedDelayString = "${group.loader.periodic-group-addition-interval}",
timeUnit = TimeUnit.SECONDS)
void loadGroups() {
final int groupsToAdd = initialStateLoaded
? periodicGroupAdditionCount : initialGroupSize;

for (int i = 0; i < groupsToAdd; i++) {
final Group group = groupService.generateGroup();
assert groupRepository != null;
groupRepository.save(group).subscribe();
}
initialStateLoaded = true;
}

@Scheduled(initialDelayString = "${group.expiry-checker.initial-check-delay}",
fixedDelayString = "${group.expiry-checker.check-interval}",
timeUnit = TimeUnit.SECONDS)
void expireGroups() {
groupService.expireGroups().subscribe();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.grouphq.groupservice.group.domain.groups;

import java.time.Instant;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
* Interface to perform Reactive operations against the repository's "groups" table.
Expand All @@ -16,4 +18,6 @@ public interface GroupRepository extends ReactiveCrudRepository<Group, Long> {
@Query("SELECT * FROM groups")
Flux<Group> getAllGroups();

@Query("UPDATE groups SET status = :status WHERE groups.created_date < :expiryDate")
Mono<Integer> expireGroupsPastExpiryDate(Instant expiryDate, GroupStatus status);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.grouphq.groupservice.group.domain.groups;

import com.github.javafaker.Faker;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
* A service performing the main business logic for the Group Service application.
Expand All @@ -10,12 +15,37 @@
public class GroupService {

private final GroupRepository groupRepository;
private final int expiryTime;

public GroupService(GroupRepository groupRepository) {
public GroupService(GroupRepository groupRepository,
@Value("${group.expiry-checker.time}") int expiryTime) {
this.groupRepository = groupRepository;
this.expiryTime = expiryTime;
}

public Flux<Group> getGroups() {
return groupRepository.findGroupsByStatus(GroupStatus.ACTIVE);
}

public Mono<Integer> expireGroups() {
final Instant expiryDate = Instant.now().minus(expiryTime, ChronoUnit.SECONDS);
return groupRepository.expireGroupsPastExpiryDate(expiryDate, GroupStatus.AUTO_DISBANDED);
}

/**
* Generates a random group that a user may have created.
*/
public Group generateGroup() {
final Faker faker = new Faker();

// Generate capacities and ensure maxCapacity has the higher number
int currentCapacity = faker.number().numberBetween(1, 249);
int maxCapacity = faker.number().numberBetween(2, 250);
final int temp = maxCapacity;
maxCapacity = Math.max(currentCapacity, maxCapacity);
currentCapacity = Math.min(currentCapacity, temp);

return Group.of(faker.lorem().sentence(), faker.lorem().sentence(20),
maxCapacity, currentCapacity, GroupStatus.ACTIVE);
}
}
11 changes: 11 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ server:
connection-timeout: 2s
idle-timeout: 15s

group:
expiry-checker:
time: 1800
initial-check-delay: 0
check-interval: 300
loader:
initial-group-delay: 0
initial-group-size: 3
periodic-group-addition-interval: 300
periodic-group-addition-count: 1

spring:
application:
name: group-service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.util.List;

@DataR2dbcTest
@Import({DataConfig.class, SecurityConfig.class})
@Tag("AcceptanceTest")
Expand Down Expand Up @@ -70,4 +72,18 @@ public void iShouldBeGivenAListOfActiveGroups() {
"All groups received should be active");
});
}

@Given("any time")
public void anyTime() {
// such as now
}

@Then("I should be given a list of at least {int} active groups")
public void iShouldBeGivenAListOfAtLeastActiveGroups(int activeGroupsNeeded) {
final List<Group> groups = groupRepository.getAllGroups().collectList().block();

assertThat(groups)
.filteredOn(group -> group.status().equals(GroupStatus.ACTIVE))
.hasSizeGreaterThanOrEqualTo(activeGroupsNeeded);
}
}
Loading

0 comments on commit c5059aa

Please sign in to comment.