diff --git a/app-model/src/main/kotlin/com/usvision/model/systembuilder/SystemBuilder.kt b/app-model/src/main/kotlin/com/usvision/model/systembuilder/CompanySystemBuilder.kt similarity index 61% rename from app-model/src/main/kotlin/com/usvision/model/systembuilder/SystemBuilder.kt rename to app-model/src/main/kotlin/com/usvision/model/systembuilder/CompanySystemBuilder.kt index 9a3190f..e36ae6a 100644 --- a/app-model/src/main/kotlin/com/usvision/model/systembuilder/SystemBuilder.kt +++ b/app-model/src/main/kotlin/com/usvision/model/systembuilder/CompanySystemBuilder.kt @@ -12,35 +12,42 @@ import com.usvision.model.systemcomposite.System class SystemBuilderException(message: String) : RuntimeException(message) -class SystemBuilder(private val parent: SystemBuilder? = null) { - private lateinit var rootName: String - private lateinit var subsystems: MutableSet +class CompanySystemBuilder(private val parent: CompanySystemBuilder? = null) { + private var rootName: String? = null + private var subsystems: MutableSet = mutableSetOf() - private fun fluentInterface(instance: SystemBuilder = this, implementation: SystemBuilder.() -> Unit): SystemBuilder { + private fun fluentInterface(instance: CompanySystemBuilder = this, implementation: CompanySystemBuilder.() -> Unit): CompanySystemBuilder { implementation() return instance } - fun setName(name: String) = fluentInterface { rootName = name } + fun setName(name: String) = fluentInterface { + this.rootName?.also { + throw SystemBuilderException("System already has a name: $rootName") + } + rootName = name + } - fun addSubsystems() = fluentInterface(SystemBuilder(this)) { - subsystems = mutableSetOf() + fun addSubsystems(): CompanySystemBuilder { + return CompanySystemBuilder(this) } - fun endSubsystems(): SystemBuilder { + fun and(): CompanySystemBuilder { + endSubsystems() + return CompanySystemBuilder(this.parent) + } + + fun endSubsystems(): CompanySystemBuilder { if (parent == null) throw SystemBuilderException("Attempted to close an environment that had not being opened") - if (this::rootName.isInitialized) { - val system = build() - parent.addSubsystem(system) - } + val system = build() + parent.addSubsystem(system) return parent } fun thatHasMicroservices(): MicroserviceBuilder { - subsystems = mutableSetOf() return MicroserviceBuilder(this) } @@ -53,15 +60,16 @@ class SystemBuilder(private val parent: SystemBuilder? = null) { } fun build(): System { - return CompanySystem(rootName).also { root -> - if (this::subsystems.isInitialized) + return this.rootName?.let { rootName -> + CompanySystem(rootName).also { root -> subsystems.forEach(root::addSubsystem) - } + } + } ?: throw SystemBuilderException("Attempted to build a System with no name") } } -class MicroserviceBuilder(private val parent: SystemBuilder? = null) { - private lateinit var name: String +class MicroserviceBuilder(private val parent: CompanySystemBuilder? = null) { + private var name: String? = null private val exposedOperations = mutableSetOf() private val consumedOperations = mutableSetOf() private val databases = mutableSetOf() @@ -73,7 +81,7 @@ class MicroserviceBuilder(private val parent: SystemBuilder? = null) { return this } - fun endMicroservices(): SystemBuilder { + fun endMicroservices(): CompanySystemBuilder { if (parent == null) throw SystemBuilderException("Attempted to close an environment that had not being opened") @@ -85,10 +93,14 @@ class MicroserviceBuilder(private val parent: SystemBuilder? = null) { fun and(): MicroserviceBuilder { endMicroservices() - return parent!!.thatHasMicroservices() + return MicroserviceBuilder(this.parent) } fun named(name: String) = fluentInterface { + this.name?.also { + throw SystemBuilderException("Microservice already has a name: $name") + } + this.name = name } @@ -117,12 +129,14 @@ class MicroserviceBuilder(private val parent: SystemBuilder? = null) { } fun build(): Microservice { - return Microservice(name).also { msvc -> - exposedOperations.forEach(msvc::exposeOperation) - consumedOperations.forEach(msvc::consumeOperation) - databases.forEach(msvc::addDatabaseConnection) - channelsPublished.forEach(msvc::addPublishChannel) - channelsSubscribed.forEach(msvc::addSubscribedChannel) - } + return this.name?.let { name -> + Microservice(name).also { msvc -> + exposedOperations.forEach(msvc::exposeOperation) + consumedOperations.forEach(msvc::consumeOperation) + databases.forEach(msvc::addDatabaseConnection) + channelsPublished.forEach(msvc::addPublishChannel) + channelsSubscribed.forEach(msvc::addSubscribedChannel) + } + } ?: throw SystemBuilderException("Attempted to build a Microservice with no name") } } \ No newline at end of file diff --git a/app-model/src/test/kotlin/com/usvision/model/systembuilder/SystemBuilderTest.kt b/app-model/src/test/kotlin/com/usvision/model/systembuilder/CompanySystemBuilderTest.kt similarity index 75% rename from app-model/src/test/kotlin/com/usvision/model/systembuilder/SystemBuilderTest.kt rename to app-model/src/test/kotlin/com/usvision/model/systembuilder/CompanySystemBuilderTest.kt index dc5ff99..b6db52e 100644 --- a/app-model/src/test/kotlin/com/usvision/model/systembuilder/SystemBuilderTest.kt +++ b/app-model/src/test/kotlin/com/usvision/model/systembuilder/CompanySystemBuilderTest.kt @@ -8,12 +8,12 @@ import io.mockk.verify import org.junit.jupiter.api.assertThrows import kotlin.test.* -internal class SystemBuilderTest { - private lateinit var underTest: SystemBuilder +internal class CompanySystemBuilderTest { + private lateinit var underTest: CompanySystemBuilder @BeforeTest fun `create clean, new instance of SystemBuilder`() { - underTest = SystemBuilder() + underTest = CompanySystemBuilder() } @Test @@ -46,10 +46,38 @@ internal class SystemBuilderTest { assertEquals(1, system.getSubsystemSet().size) val firstLevelSubsys = system.getSubsystemSet().first() assertIs(firstLevelSubsys) + assertEquals(2, firstLevelSubsys.getSubsystemSet().size) + assertIs(firstLevelSubsys.getSubsystemSet().first()) + } + + + @Test + fun `it builds a system with two subsystems`() { + + // given ... when + val system = underTest + .setName("Bank") + .addSubsystems() + .setName("Bank subsystem one") + .thatHasMicroservices() + .oneNamed("Balance API") + .exposingRestEndpoint("GET", "/account/balance", "account balance") + .endMicroservices() + .and() + .setName("Bank subsystem two") + .endSubsystems() + .build() + + // then + assertIs(system) + assertEquals(2, system.getSubsystemSet().size) + val firstLevelSubsys = system.getSubsystemSet().first() + assertIs(firstLevelSubsys) assertEquals(1, firstLevelSubsys.getSubsystemSet().size) assertIs(firstLevelSubsys.getSubsystemSet().first()) } + @Test fun `it defaults to a company system`() { // given @@ -66,7 +94,7 @@ internal class SystemBuilderTest { } @Test - fun `opening and closing a subsystem environment gives an empty subsys set`() { + fun `it throws SystemBuilderException when endSubsystems without setting a name for the subsystem`() { // given val name = "test" @@ -74,12 +102,11 @@ internal class SystemBuilderTest { val result = underTest .setName(name) .addSubsystems() - .endSubsystems() - .build() // then - assertIs(result) - assertContentEquals(listOf(), result.getSubsystemSet()) + assertThrows { + result.endSubsystems() + } } @Test @@ -113,7 +140,11 @@ internal class MicroserviceBuilderTest { @BeforeTest fun `create clean, new instance of MicroserviceBuilder`() { - underTest = MicroserviceBuilder() + val parent = spyk(CompanySystemBuilder()) + + // when + underTest = parent + .thatHasMicroservices() } @Test @@ -128,8 +159,9 @@ internal class MicroserviceBuilderTest { @Test fun `it returns its parent when closing a properly opened microservice env`() { // given - val parent = spyk(SystemBuilder()) - underTest = MicroserviceBuilder(parent) + val parent = spyk(CompanySystemBuilder()) + underTest = parent + .thatHasMicroservices() // when val environment = underTest @@ -137,7 +169,7 @@ internal class MicroserviceBuilderTest { .endMicroservices() // then - assertIs(environment) + assertIs(environment) assertEquals(parent, environment) verify { parent.addMicroservice(any()) } } @@ -158,6 +190,15 @@ internal class MicroserviceBuilderTest { assertNotNull(result.module) } + @Test + fun `it throws SystemBuilderException when building a Microservice with no name`() { + // given nothing + // when ... then + assertThrows { + underTest.build() + } + } + @Test fun `it adds exposed rest endpoints`() { // given @@ -289,12 +330,46 @@ internal class MicroserviceBuilderTest { verify { underTest.named(name) } } + @Test + fun `it throws SystemBuilderException when calling 'named' method twice`() { + // given + val name = "micro" + + // when + val result = underTest + .named(name) + + + //then + assertThrows { + result.named(name) + } + } + + + @Test + fun `it throws SystemBuilderException when calling 'and' method twice without setting a name for the second microservice`() { + // given + val name = "micro" + + // when + val result = underTest + .named(name) + .and() + + + //then + assertThrows { + result.and() + } + } + @Test fun `it offers 'and' as a convenience for finishing one and beginning another`() { // given val nameOne = "name one" val nameTwo = "name two" - val parent = spyk(SystemBuilder()) + val parent = spyk(CompanySystemBuilder()) // when parent