tonilopezmr.com Megaverse exercise
I have created an empty Spring boot project thinking I'd create an API, but finally, I have created a simple App.
That's why to run the project you can use springBoot features.
TonilopezmrApplication.kt is the code entry point where everything starts.
You can configure the TONILOPEZMR_CANDIDATE_ID
with a different CANDIDATE-ID
.
The App Creates a Megaverse from a Goal map returned by the Megaverse API.
It sends one request per second to not receive a rate limit, It can be adjusted in Coroutines.kt changing how much requests (batchSize
) you want to do in parallel per second (delayInSeconds
).
The App can be run in parallel if you increase the batchSize
, but also It can be done sequentially by changing the Coroutine Context.
I have tried to create a simple app meanwhile I showcase what I think is important.
I have decided to throws
errors to stop the App, there are many ways to handle errors, but this way the code is easy to read for this test.
In Kotlin/Java I prefer to use Result
which is a Either<A, Exception>
than throwing errors because the methods give more transparency than throwing errors.
As It's a small App, I'm not sure how structured it is, I didn't want to create so many folders / subfolders.
domain
package: Domain models of the App
usecases
package: App Actions like Get Goal Map
and Create Megaverso
services
package: External Services like MegaverseAPI
sealed class AstralObject(val x: Int, val y: Int)
data class EmptySpace(val row: Int, val column: Int) : AstralObject(row, column)
data class POLYanet(val row: Int, val column: Int) : AstralObject(row, column)
data class SOLoon(val color: Color, val row: Int, val column: Int) : AstralObject(row, column)
data class ComETH(val direction: Direction, val row: Int, val column: Int) : AstralObject(row, column)
For each AstralObject I save the x,y position for easy Objects manipulations.
AstralObject is a sealed class
to be able to know the inheritance in compile time and don't miss any case or the build won't compile.
App Layer
-> Bussines logic
-> External Services
It's important to decouple App layer from business logic layer and External Services layer, that's why each layer
uses different DTO's and Objects through mappers. Using Megaverse
instead of Array<Array<String>>
or SOLoon
instead of BLUE_SOLOON
or SOLoonBody
.
object AstralObjectBuilder {
private val possibleAstralObjects = listOf(
EmptySpaceProcessor(),
POLYanetProcessor(),
SOLoonProcessor(),
ComETHProcessor()
)
fun build(rawAstralObject: String, row: Int, column: Int): AstralObject =
possibleAstralObjects.firstNotNullOfOrNull { it.process(rawAstralObject, row, column) }
?: throw UnidentifiedAstralObject(rawAstralObject)
}
If in the future there are new Astral Objects from the API, we would create a new AstralObjectProcessor
and add it to the possibleAstralObjects
list.
private fun create(candidateId: String, megaverse: Megaverse, astralObject: AstralObject) = when (astralObject) {
is ComETH -> apiService.createCometh(candidateId, astralObject)
is POLYanet -> apiService.createPolyanet(candidateId, astralObject)
is SOLoon -> create(candidateId, megaverse, astralObject)
is EmptySpace -> false
}
Probably that will require we need to create a new AstralObject
child, and our Build will fail because we don't contemplate the option in our when
.
I have been creating the tests at the same time I develop, creating smaller cases to make sure what I'm coding is right.
90% of the tests are unit tests except:
- MegaverseAPIServiceIntegrationTest.kt which makes Network API calls.
- TonilopezmrMegaverseAppTest.kt which makes an Integration App tests, except Making Network Calls.
Execute the TonilopezmrApplication:
./gradlew bootRun
./gradlew test
Check code style:
./gradlew ktlintCheck
Check and solve the code style problems that can be solved automatically:
./gradlew ktlintFormat