How to not write inheritance code in Kotlin

When you write a couple of classes and just realise that there are functionalities that can be shared, a tendency is to create a parent / abstract class or define an utilitity class. But inheritance and global utilities are often hard to test, failed to encapsulate functionality and highly coupled. In practices, using interface offers a better design.

In this instance, I have a number of Adapter classes (in Spring) which wrap synchronous JDBC calls with a thread pool.

@Component
class ReactiveUserRelationalAdapter(
        @Autowired val repo: UserRepository,
        @Autowired @Qualifier("jdbcScheduler") val jdbcScheduler: Scheduler
): ReactiveUserService {

    val logger: Logger = LoggerFactory.getLogger(ReactiveUserRelationalAdapter::class.java)

    override fun findUserById(userId: String): Mono<UserEntity> {
        return Mono.fromSupplier {
            repo.findUserById(userId)
        }.publishOn(jdbcScheduler)
    }

    override fun save(entity: UserEntity): Mono<UserEntity> {
        return Mono.fromSupplier {
            repo.save(entity)
        }.publishOn(jdbcScheduler)
    }
}

The common things here are these parts

Mono.fromSupplier {
    // blocking call
}.publishOn(jdbcScheduler)

and

val logger: Logger = LoggerFactory.getLogger(ReactiveUserRelationalAdapter::class.java)

As mentioned, what we can do to remove the duplicate codes is to extract it into a base class, but there is a better way. With Kotlin, we can define an interface and an extension function. The interface could be named anything but generally it should describe the intention of its functionality, keep it short and to the point.

interface AsyncJdbc {
    val jdbcScheduler: Scheduler
}

fun <T, R : AsyncJdbc> R.asyncJdbc(supplier: () -> T): Mono<T> {
    return Mono.fromSupplier(supplier).publishOn(jdbcScheduler)
}

Any class that implements AsyncJdbc interface can invoke the extension function asyncJdbc. Here we pass a lambda to asyncJdbc so the function can invoke arbitrary code.

The previous adapter now can be written as

@Component
class ReactiveUserRelationalAdapter(
        @Autowired val repo: UserRepository,
        @Autowired @Qualifier("jdbcScheduler") override val jdbcScheduler: Scheduler
): ReactiveUserService, AsyncJdbc {

    override fun findUserById(userId: String): Mono<UserEntity> {
        return asyncJdbc { repo.findUserById(userId) }
    }

    override fun save(entity: UserEntity): Mono<UserEntity> {
        return asyncJdbc { repo.save(entity) }
    }
}

It now removes a fair bit of boilerplate code and heck, I don’t use inheritance here.

The same thing can be done with the logger

interface Loggable

fun <R: Loggable> R.logger(): Lazy<Logger> {
    return lazy { LoggerFactory.getLogger(this.javaClass.name) }
}

Finally, our class looks like

@Component
class ReactiveUserRelationalAdapter(
        @Autowired val repo: UserRepository,
        @Autowired @Qualifier("jdbcScheduler") override val jdbcScheduler: Scheduler
): ReactiveUserService, AsyncJdbc, Loggable {
    val LOG by logger()
    
    override fun findUserById(userId: String): Mono<UserEntity> {
        LOG.debug("find user by id...")
        return asyncJdbc { repo.findUserById(userId) }
    }

    override fun save(entity: UserEntity): Mono<UserEntity> {
        LOG.debug("save user...")
        return asyncJdbc { repo.save(entity) }
    }
}

Have fun!

Nam Nguyen
Nam Nguyen
Software engineer
comments powered by Disqus

Related