Share
Embed
Transcript
19 slides
-
Introduction to RxKotlin - Reactive Extensions for Kotlin Mario Arias
-Kotlin Yorkshire Meetup Group -
Topics About me Introduction Example: RxKotlin-Chat (RxKotlin + Spring Boot
+ RabbitMQ) RxKotlin in detail -
Software Engineer at Cake Solutions 10+ years of experience with
JVM technologies Spring certified trainer 5+ years with Scala 3+ years with Kotlin funKTionale KotlinPrimavera RxKotlin original developer and team leader* NOT an expert on reactive programming @dh44t * I hit “Merge” and “Release” buttons -
Introduction “RxJava bindings for Kotlin” - GitHub RxKotlin helps you
to use RxJava in an idiomatic way -
RxKotlin-Chat git clone [email protected]:MarioAriasC/rxkotlin-chat.git ./mvnw clean package java -jar target/rxchat-1.0.jar
--chat.name=JohnDoe —- spring.rabbitmq.host=<your.rabbitmq.server.ip> -
scanner fun scanner(): ConnectableObservable<String> { return Observable.create<String> { subscriber
try { if (subscriber.isUnsubscribed) { return@create } val scanner = Scanner(System.`in`) while (true) { val line = scanner.nextLine() if (line.equals(":q!")) { break } subscriber.onNext(line) } } catch (e: IOException) { subscriber.onError(e) } if (!subscriber.isUnsubscribed) { subscriber.onCompleted() } }.publish() } -
general fun generalObservable(name: String, input: Observable<String>): Observable<ChatMessage> { return input.filter
{ s !s.startsWith(PrivateMessage.AT) } .map { s "${PrivateMessage.AT}$name says:$s" } .map { s GeneralMessage(s) } } -
private fun privateObservable(name: String, input: Observable<String>): Observable<ChatMessage> { return input.filter
{ s s.startsWith("@") } .map { s s.split(PrivateMessage.SPACE_PATTERN, 2) } .filter { parts val condition = parts.size 2 if (!condition) { println("Wrong format") } condition } .filter { parts val condition = !parts[0].equals("@") if (!condition) { println("Invalid user") } condition } .map { parts PrivateMessage("chat.private.${parts[0].replace("@", "")}", name.says(parts[1])) } } -
chat class Chat(val context: ConfigurableApplicationContext, val name: String, general: Observable<ChatMessage>,
priv: Observable<ChatMessage>) : Observer<ChatMessage> { val subs = Observable.merge(general, priv).subscribeOn(Schedulers.io()).subscribe(this) val latch = CountDownLatch(1); val template = context[RabbitTemplateclass.java] init { template.general(GeneralMessage("$name CONNECTED")) } override fun onNext(message: ChatMessage) { when(message){ is GeneralMessage template.general(message) is PrivateMessage template.privately(message) } } override fun onError(e: Throwable) { println(e.message) } override fun onCompleted() { template.general(GeneralMessage("$name DISCONNECTED")) println("Bye") latch.countDown() subs.unsubscribe() context.close() } } -
main fun RabbitTemplate.general(message: GeneralMessage): Unit { convertAndSend(chatGeneral, "", message.body) }
fun RabbitTemplate.privately(message: PrivateMessage): Unit { convertAndSend(message.routingKey, message.body) } fun main(args: Array<String>) { //val log = LoggerFactory.getLogger(RxChatApplicationclass.java) val context = SpringApplication.run(RxChatApplicationclass.java, *args) val props = context[ChatPropertiesclass.java] val name = props.name println(""" $name, Welcome to the RxKotlin Chat -Every message that you send will published in the general chat -To send private messages, use '@' before your friend's alias (e.g.: @JohnDoe Hello) -To exit use the command ':q!' """) val scannerObservable = scanner() val general = generalObservable(name, scannerObservable) val priv = privateObservable(name, scannerObservable) val chat = Chat(context, name, general, priv) scannerObservable.connect() chat.latch.await() } -
scanner with RxKotlin fun scanner(): ConnectableObservable<String> = observable<String> { subscriber
try { if (subscriber.isUnsubscribed) { return@observable } val scanner = Scanner(System.`in`) while (true) { val line = scanner.nextLine() if (line.equals(":q!")) { break } subscriber.onNext(line) } } catch (e: IOException) { subscriber.onError(e) } if (!subscriber.isUnsubscribed) { subscriber.onCompleted() } }.publish() -
chat with RxKotlin class Chat(val context: ConfigurableApplicationContext, val name: String,
vararg obs: Observable<ChatMessage>) { val latch = CountDownLatch(1); val template = context[RabbitTemplateclass.java] init { template.general(GeneralMessage("$name CONNECTED")) obs.asIterable().merge().subscribeOn(Schedulers.io()).subscribeWith { onNext { message when (message) { is GeneralMessage template.general(message) is PrivateMessage template.privately(message) } } onError { e println(e.message) } onCompleted { template.general(GeneralMessage("$name DISCONNECTED")) println("Bye") latch.countDown() context.close() } } } } -
You don’t need RxKotlin to develop RxJava applications with Kotlin…
but code is prettier if you use it -
RxKotlin in Detail fun <T> observable(body : (s : Subscriber<in
T>) Unit) : Observable<T> = Observable.create(body) fun <T> Iterable<T>.toObservable() : Observable<T> = Observable.from(this) fun <T> Iterable<Observable<out T.merge() : Observable<T> = Observable.merge(this.toObservable()) inline fun <T> Observable<T>.subscribeWith( body : FunctionSubscriberModifier<T>.() Unit ) : Subscription { val modifier = FunctionSubscriberModifier(subscriber<T>()) modifier.body() return subscribe(modifier.subscriber) } -
Appendix: Spring Configuration (I) @Configuration @EnableRabbit open class AmqpConfiguration :
RabbitListenerConfigurer { override fun configureRabbitListeners(registrar: RabbitListenerEndpointRegistrar) { fun listener(prefix: String = ""): (Message) Unit = { message: Message println(prefix + String(message.body)) } registrar.registerEndpoint(SimpleRabbitListenerEndpoint().apply { id = "generalEndpoint" setQueues(generalQueue()) setMessageListener(listener()) }) registrar.registerEndpoint(SimpleRabbitListenerEndpoint().apply { id = "privateEndpoint" setQueues(privateQueue()) setMessageListener(listener("[PRIVATE ]")) }) } -
Appendix: Spring Configuration (II) @Bean open fun connectionFactory(props: RabbitProperties) =
CachingConnectionFactory().apply { setAddresses(props.addresses) setUsername(props.username) setPassword(props.password) } @Bean open fun template(connectionFactory: ConnectionFactory) = RabbitTemplate(connectionFactory) @Bean open fun admin(connectionFactory: ConnectionFactory) = RabbitAdmin(connectionFactory).apply { afterPropertiesSet() } @Bean open fun privateQueue() = Queue("chat.private.${chatProperties().name}") @Bean open fun generalQueue() = Queue("chat.general.${chatProperties().name}") @Bean open fun chatProperties() = ChatProperties() @Bean open fun generalFanout() = FanoutExchange("chat.general") @Bean open fun binding(generalFanout: FanoutExchange, generalQueue: Queue): Binding = BindingBuilder.bind(generalQueue).to(generalFanout)