Skip to content

Commit 131a025

Browse files
committed
changelog
1 parent 66cfe3e commit 131a025

9 files changed

Lines changed: 156 additions & 69 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
# Changelog
22

33
## Unreleased
4-
4+
- Use Markdown Architectural Decision Records https://adr.github.io/madr/
5+
- Improve maintainability by adding SpringBoot framework
6+
- Added IT (Docker tests)
7+
- Added performance tests
8+
- logback.xml can be overridden from command line
59

610
## [4.0] - 2021-18-11
711

build.gradle

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,6 @@ buildscript {
1212
if (!no_nexus && (nexus_url == "null" || nexus_user == "null" || nexus_pw == "null")) {
1313
throw new GradleException("property no_nexus='false' (or not defined) but at least one of the properties nexus_url, nexus_user or nexus_pw is not configured. Please configure those properties!")
1414
}
15-
16-
def folderRel = (String)("${project.findProperty('nexus_folder_releases') ?: System.getenv('NEXUS_FOLDER_RELEASES')}")
17-
nexusFolderReleases = folderRel == "null" ? "maven-releases" : folderRel
18-
19-
def folderSnaps = (String)("${project.findProperty('nexus_folder_snapshots') ?: System.getenv('NEXUS_FOLDER_SNAPSHOTS')}")
20-
nexusFolderSnapshots = folderSnaps == "null" ? "maven-snapshots" : folderSnaps
21-
22-
snippetsDir = file('build/generated-snippets')
2315
}
2416

2517
repositories {
@@ -58,14 +50,14 @@ buildscript {
5850

5951
plugins {
6052
id "groovy"
61-
id 'application'
62-
id "com.github.johnrengelman.shadow" version "7.1.1"
63-
id "com.google.osdetector" version "1.7.0"
6453
id 'com.adarshr.test-logger' version '3.1.0'
6554
id 'jacoco'
6655
id 'org.springframework.boot' version "${springbootVersion}"
6756
id 'com.bmuschko.docker-spring-boot-application' version '7.1.0'
57+
id "io.gatling.gradle" version "3.7.3"
6858
}
59+
// related to gatling and Springboot
60+
ext['netty.version'] = '4.0.51.Final'
6961

7062
repositories {
7163
mavenCentral()
@@ -102,7 +94,6 @@ repositories {
10294

10395
group = 'org.ods'
10496
version = '1.0'
105-
mainClassName = "org.ods.document.generation.svc.App"
10697

10798
java {
10899
toolchain {
@@ -148,9 +139,12 @@ dependencies {
148139
testImplementation "org.spockframework:spock-spring:${spockCoreVersion}"
149140
testImplementation "org.springframework.boot:spring-boot-starter-test:${springbootVersion}"
150141

151-
testImplementation "com.github.stefanbirkner:system-rules:1.19.0" // for managing environment variables
152-
testImplementation 'com.github.tomakehurst:wiremock:2.27.2' // for mocking HTTP server reponses
153-
testImplementation 'io.rest-assured:rest-assured:4.4.0' // for validating REST services
142+
testImplementation "com.github.stefanbirkner:system-rules:1.19.0"
143+
testImplementation 'com.github.tomakehurst:wiremock:2.27.2'
144+
testImplementation 'io.rest-assured:rest-assured:4.4.0'
145+
146+
gatlingImplementation 'org.awaitility:awaitility:4.1.1'
147+
gatlingImplementation 'io.rest-assured:rest-assured:4.4.0'
154148
}
155149

156150
test {
@@ -161,28 +155,42 @@ test {
161155
filter {
162156
includeTestsMatching "*Spec"
163157
}
158+
systemProperty 'com.athaydes.spockframework.report.outputDir', 'build/reports/spock'
164159
useJUnitPlatform()
165160

166161
finalizedBy jacocoTestReport
167162
}
168163

169164
task dockerTest(type: Test) {
165+
dependsOn(test)
170166
group("verification")
171167
filter {
172168
includeTestsMatching "*IT"
173169
}
170+
systemProperty 'com.athaydes.spockframework.report.outputDir', 'build/reports/spock'
174171
useJUnitPlatform()
175172
}
176173

177174
jacocoTestReport {
178175
reports {
179176
xml.enabled true
177+
html.enabled true
178+
}
179+
finalizedBy jacocoTestCoverageVerification
180+
}
181+
182+
jacocoTestCoverageVerification {
183+
violationRules {
184+
rule {
185+
limit {
186+
minimum = 0.7
187+
}
188+
}
180189
}
181190
}
182191

183192
import com.bmuschko.gradle.docker.tasks.image.*
184193
task buildImage(type: DockerBuildImage) {
185-
// dependsOn createDockerfile
186194
inputDir = file("src/main/resources")
187195
images.add('docgen-base:latest')
188196
}
@@ -194,4 +202,31 @@ docker {
194202
images = ['docgen:latest']
195203
jvmArgs = ['-Dspring.profiles.active=production', '-Xmx2048m']
196204
}
197-
}
205+
}
206+
buildImage.dependsOn(bootJar)
207+
dockerBuildImage.dependsOn(buildImage)
208+
dockerTest.dependsOn(dockerBuildImage)
209+
210+
import com.bmuschko.gradle.docker.tasks.container.*
211+
212+
task createDocGenServer(type: DockerCreateContainer) {
213+
dependsOn dockerBuildImage
214+
targetImageId dockerBuildImage.getImageId()
215+
hostConfig.portBindings = ['8080:8080']
216+
hostConfig.autoRemove = true
217+
}
218+
219+
task startDocGenServer(type: DockerStartContainer) {
220+
dependsOn createDocGenServer
221+
targetContainerId createDocGenServer.getContainerId()
222+
}
223+
224+
task stopDocGenServer(type: DockerStopContainer) {
225+
targetContainerId createDocGenServer.getContainerId()
226+
}
227+
228+
gatlingRun.group("verification")
229+
gatlingRun.dependsOn(dockerTest, startDocGenServer)
230+
gatlingRun.finalizedBy(stopDocGenServer)
231+
232+
check.dependsOn(gatlingRun)

docker/Dockerfile

Lines changed: 0 additions & 24 deletions
This file was deleted.

docs/decisions/0001-use-spring-framework.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66

77
## Context and Problem Statement
88

9-
In order to evolve DocGen service to migrate here the LevaDoc feature, initially implemented
9+
In order to evolve DocGen service and migrate here the LevaDoc feature, initially implemented
1010
in the SharedLib, we need to improve LevaDoc architecture to make it more maintainable.
1111

1212
## Decision Drivers
1313

1414
* Maintainability: speed up the development with better modularization of the code
1515
* Testability.
1616
* Extensibility.
17-
* Performance: this is not a big problem. As the API will be executed from a batch, we don't care if the response takes 2 seconds more or less
17+
* Performance (of course we should take care of Performance, but as the API will be executed from a batch,
18+
we don't care if the response takes 2 seconds more or less)
1819

1920
## Considered Options
2021

@@ -25,9 +26,9 @@ in the SharedLib, we need to improve LevaDoc architecture to make it more mainta
2526
## Decision Outcome
2627

2728
Chosen option: "SpringFramework", because:
29+
- It has the best integration with more frameworks, means also better extensibility
2830
- There's a lot of documentation and examples. Easy to solve problems
2931
- There's a bigger community of users: easy to involve new developers
30-
- Better integration with more frameworks, means also better extensibility
3132

3233
### Negative Consequences
3334

docs/decisions/0002-testing-strategy.md

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
11
# {short title of solved problem and solution}
22

3-
* Status: {proposed | rejected | accepted | deprecated | … | superseded by [ADR-0005](0005-example.md)} <!-- optional -->
4-
* Deciders: {list everyone involved in the decision} <!-- optional -->
5-
* Date: {YYYY-MM-DD when the decision was last updated} <!-- optional -->
6-
7-
Technical Story: {description | ticket/issue URL} <!-- optional -->
3+
* Status: accepted
4+
* Deciders: Sergio Sacristán
5+
* Date: 2021-12-22
86

97
## Context and Problem Statement
108

11-
{Describe the context and problem statement, e.g., in free form using two to three sentences. You may want to articulate the problem in form of a question.}
12-
13-
## Decision Drivers <!-- optional -->
9+
When unit testing a service, the standard unit is usually the service class, simple as that. The test will mock out the layer underneath in this case the DAO/DAL layer and verify the interactions on it. Exact same thing for the DAO layer mocking out the interactions with the database (HibernateTemplate in this example) and verifying the interactions with that.
1410

15-
* {driver 1, e.g., a force, facing concern, …}
16-
* {driver 2, e.g., a force, facing concern, …}
17-
*<!-- numbers of drivers can vary -->
11+
This is a valid approach, but it leads to brittle tests adding or removing a layer almost always means rewriting the tests entirely. This happens because the tests rely on the exact structure of the layers, and a change to that means a change to the tests.
12+
To avoid this kind of inflexibility, we can grow the scope of the unit test by changing the definition of the unit we can look at a persistent operation as a unit, from the Service Layer through the DAO and all the way day to the raw persistence whatever that is. Now, the unit test will consume the API of the Service Layer and will have the raw persistence mocked out in this case, the "templates.repository"
1813

19-
## Considered Options
14+
## Decision Drivers
2015

21-
* {option 1}
22-
* {option 2}
23-
* {option 3}
24-
*<!-- numbers of options can vary -->
16+
* Optimize testing effort
17+
* Improve test quality
2518

2619
## Decision Outcome
2720

28-
Chosen option: "{option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}.
21+
https://github.com/portainer/portainer
22+
https://localhost:9443/
23+
admin 12345678
2924

3025
### Positive Consequences <!-- optional -->
3126

@@ -72,9 +67,8 @@ Chosen option: "{option 1}", because {justification. e.g., only option, which me
7267
*<!-- numbers of links can vary -->
7368

7469
<!-- markdownlint-disable-file MD013 -->
75-
https://examples.javacodegeeks.com/spring-boot-mockmvc-tutorial/
7670

77-
When unit testing a service, the standard unit is usually the service class, simple as that. The test will mock out the layer underneath in this case the DAO/DAL layer and verify the interactions on it. Exact same thing for the DAO layer mocking out the interactions with the database (HibernateTemplate in this example) and verifying the interactions with that.
7871

79-
This is a valid approach, but it leads to brittle tests adding or removing a layer almost always means rewriting the tests entirely. This happens because the tests rely on the exact structure of the layers, and a change to that means a change to the tests.
80-
To avoid this kind of inflexibility, we can grow the scope of the unit test by changing the definition of the unit we can look at a persistent operation as a unit, from the Service Layer through the DAO and all the way day to the raw persistence whatever that is. Now, the unit test will consume the API of the Service Layer and will have the raw persistence mocked out in this case, the HibernateTemplate:
72+
73+
74+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.ods.doc.gen;
2+
3+
import io.gatling.app.Gatling;
4+
import io.gatling.core.config.GatlingPropertiesBuilder;
5+
6+
public class GatlingRunner {
7+
public static void main(String[] args) {
8+
GatlingPropertiesBuilder props = new GatlingPropertiesBuilder();
9+
props.simulationClass(LoadSimulation.class.getName());
10+
props.resultsDirectory("build/reports/gatling");
11+
Gatling.fromMap(props.build());
12+
}
13+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.ods.doc.gen;
2+
3+
import io.gatling.javaapi.core.ScenarioBuilder;
4+
import io.gatling.javaapi.core.Simulation;
5+
import io.restassured.http.ContentType;
6+
import org.awaitility.Awaitility;
7+
8+
import java.util.concurrent.TimeUnit;
9+
10+
import static io.gatling.javaapi.core.CoreDsl.atOnceUsers;
11+
import static io.gatling.javaapi.core.CoreDsl.exec;
12+
import static io.gatling.javaapi.core.CoreDsl.global;
13+
import static io.gatling.javaapi.core.CoreDsl.scenario;
14+
import static io.gatling.javaapi.http.HttpDsl.http;
15+
import static io.gatling.javaapi.http.HttpDsl.status;
16+
import static io.restassured.RestAssured.given;
17+
18+
public class LoadSimulation extends Simulation {
19+
20+
public static final String HEALTH = "http://localhost:8080/health";
21+
// TODO -> change POST_PDF by "http://localhost:8080/document" and test a big doc
22+
public static final String POST_PDF = "http://localhost:8080/health";
23+
public static final int STATUS_CODE_OK = 200;
24+
25+
public static final int EXECUTION_TIMES = 100;
26+
27+
// Assertions: https://gatling.io/docs/gatling/reference/current/core/assertions/
28+
public static final double SUCCESSFUL_REQUEST_PERCENT = 100.0;
29+
public static final int MAX_RESPONSE_TIME = 400;
30+
public static final int MEAN_RESPONSE_TIME = 245;
31+
32+
ScenarioBuilder scn = scenario( "postPDF").repeat(EXECUTION_TIMES).on(
33+
exec(
34+
http("POST_PDF")
35+
.get(POST_PDF)
36+
.asJson()
37+
.check(status().is(STATUS_CODE_OK))
38+
).pause(1)
39+
);
40+
41+
{
42+
waitUntilDocGenIsUp();
43+
setUp(scn.injectOpen(atOnceUsers(1))).assertions(
44+
global().successfulRequests().percent().is(SUCCESSFUL_REQUEST_PERCENT),
45+
global().responseTime().max().lt(MAX_RESPONSE_TIME),
46+
global().responseTime().mean().lt(MEAN_RESPONSE_TIME)
47+
);;
48+
}
49+
50+
private void waitUntilDocGenIsUp() {
51+
Awaitility.await().atMost(60, TimeUnit.SECONDS).pollInterval(5, TimeUnit.SECONDS).until(() ->
52+
{
53+
return given().contentType(ContentType.JSON).when().get(HEALTH).getStatusCode() == STATUS_CODE_OK;
54+
});
55+
}
56+
}

src/main/resources/logback.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
</layout>
1010
</appender>
1111

12-
<logger name="org.ods" level="trace" additivity="false">
12+
<!-- https://logback.qos.ch/manual/configuration.html#defaultValuesForVariables -->
13+
<logger name="org.ods" level="${ODS_LOG_LEVEL:-INFO}" additivity="false">
1314
<appender-ref ref="STDOUT"/>
1415
</logger>
15-
<root level="INFO">
16+
<root level="${ROOT_LOG_LEVEL:-INFO}">
1617
<appender-ref ref="STDOUT" />
1718
</root>
1819
</configuration>

src/test/groovy/org/ods/doc/gen/DocGenDockerIT.groovy

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.ods.doc.gen
22

3+
import groovy.util.logging.Slf4j
34
import io.restassured.http.ContentType
45
import org.testcontainers.containers.GenericContainer
6+
import org.testcontainers.containers.output.Slf4jLogConsumer
57
import org.testcontainers.spock.Testcontainers
68
import org.testcontainers.utility.DockerImageName
79
import spock.lang.Shared
@@ -10,17 +12,22 @@ import spock.lang.Specification
1012
import static io.restassured.RestAssured.given
1113
import static org.hamcrest.Matchers.equalTo
1214

15+
@Slf4j
1316
@Testcontainers
1417
class DocGenDockerIT extends Specification {
1518

1619
static final DockerImageName DOCGEN_IMAGE = DockerImageName.parse("docgen:latest");
1720

1821
@Shared
19-
GenericContainer<?> docGenContainer = new GenericContainer<>(DOCGEN_IMAGE).withExposedPorts(8080);
22+
GenericContainer<?> docGenContainer = new GenericContainer<>(DOCGEN_IMAGE)
23+
.withExposedPorts(8080)
24+
.withEnv("ROOT_LOG_LEVEL", "TRACE")
2025

2126
def "docgen is running in docker"() {
2227
given:
2328
def port = docGenContainer.firstMappedPort
29+
Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log)
30+
docGenContainer.followOutput(logConsumer)
2431

2532
expect:
2633
given()

0 commit comments

Comments
 (0)