I was wondering what is the correct way to organize my class hierarchy in the following situation.
I wanted to build an abstraction around postgresql advisory lock.
Note just for context: An advisory lock is a lock that you can obtain at a session or transaction level. Postgres handle all the complexity for you.
The code that I've written so far is something like
interface DBLockService
interface SessionLockService : DBLockService {
fun acquire(id: Long)
fun unlock(id: Long): Boolean
}
interface TransactionalLockService : DBLockService {
fun txAcquire(id: Long)
}
abstract class BaseDBLockService(protected val entityManager: EntityManager): DBLockService {
protected fun executeAcquire(preparedStatement: String, id: Long) {
executeAcquire<Any>(preparedStatement, id)
}
protected inline fun <reified T> executeAcquire(preparedStatement: String, id: Long) =
entityManager
.createNativeQuery(preparedStatement, T::class.java)
.setParameter("id", id)
.singleResult as T
}
@Component
class LockServiceImpl(
entityManager: EntityManager
) : BaseDBLockService(entityManager),
SessionLockService {
companion object {
const val acquireStatement = "SELECT pg_advisory_lock(:id)"
const val unlockStatement = "SELECT pg_advisory_unlock(:id)"
}
override fun acquire(id: Long) {
executeAcquire(acquireStatement, id)
}
override fun unlock(id: Long) =
executeAcquire<Boolean>(unlockStatement, id)
}
@Component
class TransactionalLockServiceImpl(
entityManager: EntityManager
) : BaseDBLockService(entityManager),
TransactionalLockService {
// very similar implementation
}
Looking at this code there is something that tell's me that there is something wrong:
DBLockServiceis a bit useless interface, there is no method- Are
SessionLockServiceandTransactionalLockServicejust an implementation detail? Is it correct that there is a different interface for every "type" of lock?
But at the same time, if I remove the DBLockService seems very odd to me that there are 2 interfaces (SessionLockService and TransactionalLockService) with very similar context that are not related in any way.
Moreover, removing DBLockService, I'll have the 2 implementations (LockServiceImpl and TransactionalLockServiceImpl) that extends from the abstract class BaseDBLockService to implement these 2 interfaces but at the same time the abstract class is not related to them.
What to you think?
Thanks
Update
As requested I'll add an example of a real case scenario
@Service
class SomethingService(private val lockService: TransactionalLockService){
@Transactional
fun aMethod(entityId: Long){
lockService.txAcquire(entityId)
//code to be synchronized or there will be problems
}
}
I would like to inject a class of a generic LockService but I cannot find a way to abstract that because imho a lock that disappear after the transaction ends is a lock different from a lock that disappear after the connection to the db is closed (session lock) that is different from a lock that need to be unlocked automatically.
It's possible that there are a lot of other implementations of lock, for example a TimeoutLock that remove the lock after some time.
But I'm not able to think how to separate these implementation details from the general concept of a Lock.
acquirein the common interface seems a bit odd to me because it's not clear what is the contract of that methodServiceseems like a bit of a misnomer to me. MaybeStrategyis more appropriate? I image some type ofConnectionclass would use either and/or both implementations, is that correct?