spring-projects / spring-data-commons

Spring Data Commons. Interfaces and code shared between the various datastore specific implementations.
https://spring.io/projects/spring-data
Apache License 2.0
783 stars 676 forks source link

How to override methods in a custom coroutine repository #2729

Closed koenpunt closed 1 year ago

koenpunt commented 2 years ago

I'm trying to implement a SoftDeleteRepository abstraction, but since there's no concrete implementation of the CoroutineCrudRepository (or any coroutine repository for that matter), how am I supposed to achieve this?

Contrived example of what I'm working with;

@NoRepositoryBean
interface SoftDeleteRepository<T, ID> {
    suspend fun findById(id: ID): T?
    suspend fun deleteById(id: ID)
}
class SoftDeleteRepositoryImpl<T, ID: Any>(
    private val entity: RelationalEntityInformation<T, ID>,
    private val entityOperations: R2dbcEntityOperations,
    private val converter: R2dbcConverter
) : SoftDeleteRepository<T, ID> {

    // ...

    override suspend fun findById(id: ID): T? {
        return entityOperations.selectOne<T>(getIdQuery(id), entity.javaType).awaitSingle()
    }

    override suspend fun deleteById(id: ID) {
        TODO("Not yet implemented")
    }
}
interface ProductVariantRepository : SoftDeleteRepository<ProductVariant, UUID>, CoroutineCrudRepository<ProductVariant, UUID> {
    // ...
}

The problem; when for example performing the findById method it never reaches the custom implementation.

Related: https://github.com/spring-projects/spring-data-commons/issues/2379

mp911de commented 2 years ago

Coroutine repositories are built on top of reactive repositories by adapting Coroutine infrastructure to Reactive return types. There's no concrete implementation for CoroutineCrudRepository because the invocation proxy for Coroutine repositories uses SimpleReactive…Repository implementations.

We do not natively support coroutine extensions because of the mentioned adapter mechanism. If you want to extend a Coroutine repository you need to provide an extension to a SimpleReactive…Repository using the reactive Java mechanisms.

koenpunt commented 2 years ago

Coroutine repositories are built on top of reactive repositories by adapting Coroutine infrastructure to Reactive return types. There's no concrete implementation for CoroutineCrudRepository because the invocation proxy for Coroutine repositories uses SimpleReactive…Repository implementations.

This was clear to me, also from the linked issue.

We do not natively support coroutine extensions because of the mentioned adapter mechanism. If you want to extend a Coroutine repository you need to provide an extension to a SimpleReactive…Repository using the reactive Java mechanisms.

How can such a repository be exposed with suspend fun's? Because the interface has to be defined with reactive types. Or isn't that possible? That would be fairly disappointing..

mp911de commented 2 years ago

suspend is a Kotlin feature that the Spring Data adopts to if such a method is invoked.

Because the interface has to be defined with reactive types

Exactly. Check out how SimpleReactiveMongoRepository maps to the Coroutine repository declaration. If you want to expose a suspend method, then the implementation uses Mono<…> as return type. For Flow, you would return Flow.

Let me know whether that helps.

koenpunt commented 2 years ago

If you want to expose a suspend method, then the implementation uses Mono<…> as return type. For Flow, you would return Flow.

I tried this, but how should I import/implement this interface?

What I'm trying to accomplish is having a custom "base repository" for entities that have to support soft deletion.

So ProductRepository -> SoftDeleteRepository -> ???Repository.

If ???Repository would be an interface with methods defined like SimpleReactiveMongoRepository, then how can a implementation importing the ProductRepository invoke suspend fun's on it?

mp911de commented 2 years ago

You're right. Fragments are discovered by naming pattern and expected to implement an interface. I am afraid that this is going to prevent your approach to discover fragments out of the box. You could post-process the actual bean definition (RepositoryFactoryBeanSupport.setRepositoryFragments(…)) to include repository fragments (RepositoryFragment.implemented(…)) for that purpose.

For base repositories, you can specify a subclass of Simple…Repository via @Enable…Repositories(repositoryBaseClass = …).

Spring Data introspects all repository fragments (base repository, default custom implementation, implemented fragments and configuration-contributed ones such as Querydsl) to find a method to invoke.

Let me know how this goes.

koenpunt commented 2 years ago

Thank you for these details. So if I understand correctly; I have to create a SoftDeleteRepository implementation defining reactive methods, inheriting from Simple...Repository. Additionally I would define a SoftDeleteCoroutineRepository interface that defines the same methods, but with a coroutine signature, and then by defining repository fragments I should be able to glue the two together?

mp911de commented 2 years ago

and then by defining repository fragments I should be able to glue the two together?

If you specify SoftDeleteRepository as repositoryBaseClass, then this should be sufficient.