Greenfield software development :-)
# CODE
# ORGANIZING CODE
Dependency rule:
source code dependencies can only point inwards
# ORGANIZING CODE
Grouping source code in
# ORGANIZING CODE
# ORGANIZING CODE
P08389-CPCM-frontend
Javascript code
cpcm-frontend-0.0.12.jar
P08389-CPCM
Java/Kotlin code
P08389-outgoing-frontend
P02733-incoming-bmg
<dependency> <groupId>nl.mendesgans.cpcm</groupId> <artifactId>cpcm-frontend</artifactId> <version>0.0.12</version> </dependency>
# KOTLIN
# KOTLIN
compile
*.kt
*.java
Bytecode
decompile
compile
decompile
JVM
jre 1.8+
kotlinc
javac
// MyClass.java
public class MyClass {
public static void main(final String[] args) throws Exception {
System.out.println("Hello world!");
}
}# KOTLIN
// MyClass.kt
fun main(args: Array<String>) =
println("Hello world!")// MyClass.java
public class MyClass {
private static final String DASHES = "--";
public static void main(final String... args) {
new MyClass()
.run("Hello", text -> System.out.println(DASHES + " " + text + " "))
.run("world!", text -> System.out.println(text));
}
private MyClass run(final String text, final Consumer<String> task) {
task.accept(text);
return this;
}
}# KOTLIN
// MyClass.kt
fun main(vararg args: String) =
MyClass()
.run("Hello", { text -> println("$DASHES $text ") })
.run("world!") { println(it) }
class MyClass {
companion object {
const val DASHES = "--"
}
fun run(text: String, task: Consumer<String>): MyClass {
task.accept(text)
return this
}
}# ARCHITECTURE
// entity
open class BasicRate {
lateinit var identifier: String
val lifeSpan = LifeSpan()
val validity = Validity()
lateinit var currencyCode: CurrencyCode
lateinit var rateType: InterestRateType
var bidRate: BigDecimal? = null
var askRate: BigDecimal? = null
var midRate: BigDecimal? = null
}
interface BasicRateFactory {
fun fetchAll(): Collection<BasicRate>
fun store(modifiedBasicRates: Collection<BasicRate>)
}// use case
@Service
class DataConsistencyService(
private val basicRateFactory: BasicRateFactory
) {
fun verify() =
basicRateFactory.fetchAll()
.map { it.validity }
.sortedBy { it.validFrom }
.zipWithNext()
.filter { it.first.overlapsWith(it.second) }
.map { "BasicRate of ${it.first.validFrom} overlaps with ${it.second.validFrom}" }
}# ARCHITECTURE
// persistency
@Entity
@Table(schema = "CPCM", name = "BasicRate")
internal class DbBasicRate {
@Id
lateinit var identifier: String
lateinit var validFrom: LocalDate
var validTo: LocalDate? = null
@Column(columnDefinition = "CHAR(3)")
lateinit var currencyCode: CurrencyCode
@Column(precision = RATE_PRECISION, scale = RATE_SCALE)
var bidRate: BigDecimal? = null
// ...
}@Repository
private interface DbBasicRateRepository : JpaRepository<DbBasicRate, Int>@Component
private class DbBasicRateFactory(
private val dbBasicRateRepository: DbBasicRateRepository
) : BasicRateFactory {
override fun fetchAll() =
dbBasicRateRepository.findAll()
.map(DbBasicRateAdapter::toBasicRate)
// ...
}# ARCHITECTURE
// persistency
internal object DbBasicRateAdapter {
fun toDbBasicRate(basicRate: BasicRate) =
DbBasicRate().apply {
identifier = basicRate.identifier
createdBy = basicRate.lifeSpan.createdBy
createdAt = basicRate.lifeSpan.createdAt
terminatedAt = basicRate.lifeSpan.terminatedAt
validFrom = basicRate.validity.validFrom
validTo = basicRate.validity.validTo
currencyCode = basicRate.currencyCode
rateType = basicRate.rateType
// ...
}
fun toBasicRate(dbBasicRate: DbBasicRate) =
BasicRate().apply {
identifier = dbBasicRate.identifier
lifeSpan.createdBy = dbBasicRate.createdBy
lifeSpan.createdAt = dbBasicRate.createdAt
lifeSpan.terminatedAt = dbBasicRate.terminatedAt
validity.validFrom = dbBasicRate.validFrom
validity.validTo = dbBasicRate.validTo
currencyCode = dbBasicRate.currencyCode
// ...
}
}# LIQUIBASE
liquibase-core-4.17.2.jar
Liquibase wrapper class
fun main()
Maven
# LIQUIBASE
Liquibase wrapper class
CPCM application
Unit Integration Test
Testcontainers
# TESTCONTAINERS
internal class DbContainer private constructor() :
MSSQLServerContainer<DbContainer>("mcr.microsoft.com/mssql/server:latest") {
companion object {
val singleton by lazy { DbContainer() }
}
override fun start() {
acceptLicense()
// Start the Docker container.
super.start()
// Create database 'NCP' and then update schema 'CPCM' using Liquibase.
createDatabase(jdbcUrl, username, password) // DbContainer.username = "sa"
updateSchema(jdbcUrl, DBO_USER, DBO_PASSWORD, "UNIT_TEST")
}
}# TESTCONTAINERS
// This extension must be triggered BEFORE any extension that requires database access.
private class StartTestcontainersBeforeAll : BeforeAllCallback {
override fun beforeAll(extensionContext: ExtensionContext) {
// Start the database in a Docker container if it is not already running.
if (!DatabaseContainer.singleton.isRunning) {
DatabaseContainer.singleton.start()
}
}
}
@SpringBootTest(classes = [CPCMApplication::class])
@TestPropertySource("classpath:application-test.properties")
@ExtendWith(StartTestcontainersBeforeAll::class)
interface AbstractTestcontainersIT {
companion object {
@DynamicPropertySource
@JvmStatic
fun applyContainerPropertiesToSpring(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.url") { DatabaseContainer.singleton.jdbcUrl }
registry.add("spring.datasource.username") { DB_USER }
registry.add("spring.datasource.password") { DB_PASSWORD }
}
}
}# ASCIIDOC