spring-projects / spring-integration-extensions

The Spring Integration Extensions project provides extension components for Spring Integration
http://www.springintegration.org/
280 stars 266 forks source link

Kotlin DSL #218

Closed Lewik closed 4 years ago

Lewik commented 5 years ago

Enhancement

Kotlin is "very" type safe. I didn't found any type safety in Java DSL. I think it's a good idea to see type problems right in IDE. It's impossible to do while using .handle("someBean") but it's possible when using beans from method definition and arguments.

Scratch:


class Builder<IN>(
    private val siBuilder: IntegrationFlowBuilder
) {
    fun <OUT> handle(beanName: String): Builder<OUT> {
        siBuilder //handle beanName
        return Builder(siBuilder)
    }

    fun <OUT> handle(lambda: (IN) -> OUT): Builder<OUT> {
        siBuilder //handle lambda
        return Builder(siBuilder)
    }

    fun channel(): Builder<IN> {
        siBuilder //handle channel
        return this
    }

    companion object {
        fun <IN> from(messageChannelName: String) : Builder<IN>{
            //val siBuilder = IntegrationFlows.from(messageChannelName)
            return Builder(SiBuilder())
        }
    }
}

class SomeBean{
    fun handleIntToString(int: Int) = ""
    fun handleStringToInt(string: String) = 1
}

@Configuration
class SomeConfig {
    @Bean
    fun methodBean() = SomeBean()

    @Bean
    fun someConfig(
        argumentBean: SomeBean
    ) {
        Builder
            .from<String>("channel")
            .handle<Int>("strToIntBean")
            .handle<Double>("strToDoubleBean")
            .handle { it.toString() }
            .handle { it.length }
            .channel()
            .handle { methodBean().handleIntToString(it) }
            .channel()
            .handle { argumentBean.handleStringToInt(it) }
    }
}

All fluent calls in someConfig know about types except calls with string argument - there is no way to check input type. In this case, we can force a user to write exactly the input type just for himself.

I am not sure in this implementation (I am not good in types, generics). Can kotlin programmers look at this idea?

artembilan commented 5 years ago

Looks like "generics hell". I really don't see reason to abuse that type system in Kotlin for this DSL purpose. It just doesn't bring too much value. The point is that Spring Integration components are really type-agnostic and we just don't care what is an input since it can be converted internally into an expected type. What you suggest here is just a syntax sugar on typing in the IDE. Those input/output generics just don't bring any value at runtime. And we really can't infer those provided generics for our conversion purpose because of type erasure anyway.

To conclude: even if Kotlin type system is so powerful, it doesn't help to make Spring Integration processing smarter. Whenever it is possible to be tied to types with lambdas or some other functions, we expose that hook. In other places we just rely on whatever comes at runtime and tries to convert it into expected type. Or even better we try to select an appropriate method to call, first of all, according to the type of input. In other words: Spring Integration doesn't care about configuration types, just because everything is postponed to runtime conversion mechanism.

I'm still open for Kotlin DSL in Spring Integration, but I don't think this type safety is the direction we need to pursue.

Thank you for understanding.

Leaving open until your feedback, but for me there is nothing to implement in this issue yet...

Lewik commented 5 years ago

"everything is postponed to runtime conversion mechanism" It's better to get an error at compile time or at application start. My main goal was errors on compile time. As I said kotlin is "very" type safe, and Java DSL feels as not type safe.

Anyway, my generics will not help the underlying SI code in some cases, this is true. If type safety is not the goal of SI, then I will try to create a 3rd party lib for it.

If anybody have other ideas for Kotlin DSL - feel free to text me.

artembilan commented 5 years ago

You have type safety in your custom functions, all the out-of-the-box components in Spring Integration (header enricher, aggregator, splitter etc.) are type-agnostic and you rally can send anything in payload to them for processing. And even with your custom, type-based function, there is still no type-tying because there is a runtime conversion system. So, anything comming in the payload is going to be converted to expected type. Therefore all those compile-time generics don't bring too much value. Just because dealing with them would restrict a runtime conversion system. Plus it would confuse you that even if you have type-safety, it doesn't work at runtime and you are good to send anything else, not only an expected compile-time type.

That's why I don't see reason to add extra pain for end-user minds to deal with those generics since all the types are ignored and going to be converted at runtime. You may produce Foo from one endpoint and the next one may expect Bar - and it is still valid flow for Spring Integration: the Foo is going to be converted to Bar at runtime!

The main goal of Spring Integration as an EIP Framework to ensure a loose coupling principle between producer and consumer, so they don't care about payload types of each other. I believe even with Kotlin DSL we still need to be EIP-centric.

On the other hand, I think there are other feature in Kotlin to consider to implement as a DSL for Spring Integration, so I still can keep this issue open and try to gather any possible thoughts on the matter.

Although I need to learn Kotlin more to become as a good interlocutor 😄

Thanks

Lewik commented 5 years ago

"Loose coupling" - is a good practice.

the Foo is going to be converted to Bar at runtime loose coupling principle between producer and consumer, so they don't care about payload types of each other

How exactly Foo will be converted into Bar?

artembilan commented 5 years ago

See here: https://docs.spring.io/spring-integration/docs/5.2.0.BUILD-SNAPSHOT/reference/html/configuration.html#message-mapping-rules and here: https://docs.spring.io/spring-integration/docs/5.2.0.BUILD-SNAPSHOT/reference/html/messaging-endpoints.html#payload-type-conversion as well as the next Content Type Conversion paragraph.

artembilan commented 4 years ago

Recently on Gitter we have bumped to issue like this:

 IntegrationFlows.from(CharacterStreamReadingMessageSource.stdin()) { e -> e.poller(Pollers.fixedDelay(1000)) }

Kotlin can't determine which method to call. This fixes the problem:

IntegrationFlows.from<Any>({ CharacterStreamReadingMessageSource.stdin().receive() }) { e -> e.poller(Pollers.fixedDelay(1000)) }

Pay attention for wrapping MessageSource call into a Supplier.

I wonder if we should consider to make a MessageSource as a Supplier and remove respective IntegrationFlows.from(MessageSource) at all...

artembilan commented 4 years ago

See here BTW: https://github.com/spring-projects/spring-integration/pull/3060

artembilan commented 4 years ago

The decision is to start spring-integration-kotlin-dsl extensions project. So, moving this issue into appropriate GH repo.