Hey guys, I have a value object which holds a stri...
# announcements
p
Hey guys, I have a value object which holds a string:
Copy code
data class SystemName(val value: String) {
    init {
        require(value == value.toLowerCase().capitalize()) { "Name $value must be capitalized!" }
    }
Right now, when I create the Object I always check if its lowercase with the first letter being capital. I'd like to let the object take care of
.toLowercase().capitalize()
, so I don't have to do it on my own. Searching google gives me mixed results which don't look that clean tbh. What would be your take on this? Thanks ahead! My current take is the following:
Copy code
data class SystemName(val value: String) {
    init {
        require(value == value.toLowerCase().capitalize()) { "Name $value must be capitalized!" }
    }

    /**
     * automatically converts string to required format
     */
    companion object {
        fun createInstance(name: String) = SystemName(name.toLowerCase().capitalize())
    }
h
You can make the constructor private. only having a factory method helps not to cause exceptions. Could happen with Data classes copy method though. If you don't need the wrapper to be a data class, you could use a regular class, so No constructor is leaked and the user of the class never has to handle exceptions.
p
How would that look as code? Thanks for the suggestion!
h
Got another idea which works without factory but with a constructor alone. class SystemName(uncleanedValue: String) { val value = uncleanedValue.toLowercase().capitalize() } As mentioned i don't know if you need equals and hashcode from data classes.
p
I guess I had a rubber duck problem.. I realized that I only use the value object to compare it to another string. I basically only use it as follows:
Copy code
return when (system.toString()) {
            System.Abc.name -> dosmth
            System.Def.name -> dosmth
            else -> throw SystemNotFoundException("System $system could not be found")
        }
So I guess just overriding the toString method would be way easier.
šŸ‘ 1
Thanks for your input! Often its already useful to look at it from a different point of view. šŸ˜„
h
Ah, ok, guess there's more to your code then ;) If the given snippet is all you do with the system Name and you only treat a known Set of systems, you might want to consider using a sealed class or an enum. Don't know whether that stuff is maybe too much and you just wanna get sth finished though
p
Yeah, I thought about using a sealed class, but didn't really get the grasp yet. How would you go for that? I basically just have a set of systems and check if my system is one of them.
h
If you have a completely fixed set of systems, make them an enum. I assume that your one given System is some Kind of user input String identifier. Than write a factory function SystemEnum.Companion.from(value: String): SystemEnum? That returns null If the given String does Not identify a system. Afterwards you can completely work on a proper system instance or return early if the input wasnt valid in the First place. You can implement doSth methods directly in the enum. Things are a Bit different when your stuff is more dynamic, but take your time and don't get too much influenced by me, i don't know much about your project and guess a lot ;)
p
Well, you guessed right! Its a fixed set of systems and there's an input which has to match one of these.
m
So in this case I would definitely go with Hannes suggestion: code the set as an enum. About the factory, it already exists in std library as
enumValueOf(name)
but it throws if the string value doesn’t match. If you want a null in that case you can wrap it as a top-level function like this:
Copy code
inline fun <reified E : Enum<E>> enumValueOrNull(name: String): E? = try {
    enumValueOf<E>(name)
} catch (e: Exception) {
    null
}
As for your doSth function, it could be defined inside the enum but consider if that behavior belongs to SystemName or not.
p
Throwing an exception is no problem in that case. My current version looks like that:
Copy code
class SystemName(val value: String) {
    override fun toString(): String = value.toLowerCase().capitalize()
}

/**
 * consists of all integrated systems
 * used to determine the workflow on parsing and mapping the objects
 */
enum class System {
    System1,
    System2;

    companion object {
        fun of(systemName: SystemName): System = enumValueOf(systemName.toString())
    }
}

class FindConfigurationService(
        val findDataUsecase: FindDataUsecase,
        val findOtherDataUseCase: FindOtherDataUseCase
) : FindConfigurationUseCase {
    override fun getConfiguration(systemName: SystemName, id: ConfigId): Configuration =
            when (System.of(systemName)) {
                System.System1 -> findDataUsecase...
                System.System2 -> findOtherDataUseCase...
            }
}
It's working and I'm fine with that, but I'm definetly open for suggestions!
m
looks good to me, definition of SystemName and enum parsing are well separated, I think it’s fine. The only change you can consider is if the functions ā€œfindDataUsecaseā€¦ā€ could have a correct semantic if moved inside `System`; I mean that their operation is something that a System constant should know how to perform or if shouldn’t be concerned with that responsibility. In that case you can remove the
when
expression and simply have:
Copy code
System.of(systemName).findUseCase()
p
Yeah, I thought about that, too. The version above fits better from an architectural standpoint which you can't be aware of: it's a hexagonal architecture, so the enum is in the domain package, while the business logic is in the application package. Thanks for the discussion guys!
āž• 1
m
Big +1 for Hex architecture, I’m a fan of those, I hope to work or develop some project using it someday. You’re welcome, it’s always a pleasure to confront on code design issues.
p
I don't want to spoil too much, but it's a pleasure to work with. Way easier to decide where to put things.
šŸ‘ 1
h
I wasn't aware of the Name hexagonal architecture tbh, but i do every piece of code like that. If i understood the pattern correctly, it follows naturally when one works modularized and with inversion of control a lot. Thx for the discussion!
m
Well it’s not exactly like that… sure modularization and IoC are some building blocks of this architecture, but not the sole ones. The important part is that its layers can be imagined as concentric, where the innermost one is the domain and going outwards you have application and platform concerns. At the edge lie ā€œports and adaptersā€, dealing with external entities, such as filesystem, network, etc. The main difference with the usual 3-tier architectures is that Hex is not data-centric, meaning that the layers and entities are not organized around a database. Have a read here: https://web.archive.org/web/20200608173349/https://alistair.cockburn.us/hexagonal-architecture/ Similar architectures are ā€œOnionā€ and Uncle Bob’s ā€œClean Architectureā€.