Reader small image

You're reading from  Kotlin Design Patterns and Best Practices - Third Edition

Product typeBook
Published inApr 2024
PublisherPackt
ISBN-139781805127765
Edition3rd Edition
Right arrow
Author (1)
Alexey Soshin
Alexey Soshin
author image
Alexey Soshin

Alexey Soshin is a software architect with 18 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design
Read more about Alexey Soshin

Right arrow

Practical Functional Programming with Arrow

After discussing the functional programming principles in Kotlin, the concurrency primitives, and some of the concurrent design patterns, we can now investigate more pragmatic applications of those topics using the Arrow library for Kotlin as an example. Arrow aims to bring idiomatic functional programming to Kotlin. This means Arrow is inspired by the great work done in other functional programming communities, such as Scala, yet it exposes these ideas and concepts in ways that feel natural to Kotlin programmers.

Arrow comprises different libraries, each improving or extending a commonly used library in the Kotlin ecosystem or a particular Kotlin language feature.

In this chapter, we’ll cover the following topics:

  • Getting started with Arrow
  • Typed errors
  • High-level concurrency
  • Software transactional memory
  • Resilience
  • Circuit Breaker
  • Saga
  • Immutable data

After reading...

Technical requirements

In addition to the technical requirements from the previous chapters, you will also need a Gradle-enabled Kotlin project to be able to add the required dependencies.

You can find the source code used in this chapter on GitHub at the following location:

https://github.com/PacktPublishing/Kotlin-Design-Patterns-and-Best-Practices_Third-Edition/tree/main/Chapter10.

Getting started with Arrow

First, let’s open build.gradle.kts and add a new dependency:

{
    ...
    implementation("io.arrow-kt:arrow-core:1.2.3")
}

This will add the required library to our project. We’ll use version 1.2.3 for the examples. If, by the time you read this chapter, a new library version has been released, IntelliJ IDEA will highlight the version for you and suggest you update it.

Once ready, we can begin exploring the various aspects of the Arrow library. We’ll start with typed errors, which will provide us with an alternative to Kotlin exceptions.

Typed errors

Functional programming commonly advocates for explicitly stating potential errors in a function’s type signature, a practice enforced at compile time to encourage proactive error management. This approach is similar to Java’s checked exceptions, where errors must be either handled or propagated to avoid compilation failure.

Kotlin, along with many contemporary languages, favors unchecked exceptions. This choice places the responsibility of understanding and managing anticipated errors on the programmer, usually through documentation or code reviews, or with fallback try-catch blocks in the outer layers of the application to prevent throwing unhandled exceptions to the caller.

Typed errors offer a more transparent alternative, integrating error handling as an essential component of a function’s definition.

“Logical failures” are domain-specific, predictable outcomes that do not meet success criteria. For example, in a user...

High-level concurrency

Coroutines are a standout feature in Kotlin, offering advanced capabilities for managing asynchronous computations. While the standard library provides essential coroutine support, it sometimes falls short in more complex scenarios. The Arrow library fills this gap, offering additional functions and primitives that are useful in Kotlin and other programming languages.

To utilize these features, include the following dependency in your project:

implementation("io.arrow-kt:arrow-fx-coroutines:1.2.1")

Parallel operations

In Chapter 8, Designing for Concurrency, while discussing the Barrier design pattern, we saw the following implementation:

val characters: List<Deferred<FavoriteCharacter>> =
    listOf(
        Me.getFavoriteCharacter(),
        Nana.getFavoriteCharacter(),
        Sebastian.getFavoriteCharacter()
    )
 
println(characters.awaitAll())

Arrow’s parZip function is a prime example of enhancing...

Software transactional memory

Software Transactional Memory (STM) is a powerful abstraction designed for modifying state in concurrent programming. It enables the writing of code that accesses shared state concurrently, facilitating easy composition while maintaining safety guarantees. One of the key advantages of using STM is that it prevents deadlocks and race conditions in programs running within its transactions. The foundational elements of STM are Transactional Variables or TVars.

Conceptually, a TVar is a wrapper around a variable that adds a layer of protection against concurrent modifications. Modifying a TVar requires operating within the STM context. This can be achieved by writing an extension function with STM as the receiver. This approach ensures that modifications to shared state are safely managed within the structured framework of STM, reducing the complexity and potential errors associated with concurrent state changes.

In order to work with Arrow STM, we...

Resilience

Resilience is a fundamental aspect of modern software systems, particularly those that rely on the cooperation of various services. These services can be local (within the same process or machine) or remote, requiring network communication. Such distributed architectures inherently introduce numerous potential points of failure. Resilience refers to the system’s capacity to respond and adapt effectively to such failures.

To explore resilience concepts in practice, it’s necessary to include specific dependencies in your project. For instance, when working with the Arrow library in Kotlin, you can add the following dependency to enable resilience features:

implementation("io.arrow-kt:arrow-resilience-jvm:1.2.1")

The approach to building a resilient system varies based on several factors, such as the feasibility of retrying requests, the criticality of errors, and the need for administrator intervention in case of fatal issues. Arrow doesn...

Circuit Breaker

The Circuit Breaker design pattern, inspired by electrical engineering, is essential for managing service availability in software systems. Its primary role is to protect an overloaded service by failing fast, thus maintaining system stability and preventing cascading failures in distributed systems.

The Circuit Breaker implements the State design pattern, and it has three states:

  1. Closed State:
    1. The default state where requests are processed normally.
    2. Each exception increments a failure counter.
    3. The Circuit Breaker transitions to the Open state when the failure counter exceeds a specified threshold (maxFailures).
    4. A successful request resets the failure count to zero.
  2. Open State:
    1. In this state, the Circuit Breaker short-circuits all requests by throwing an ExecutionRejected exception.
    2. If a request is made after a configured reset timeout, the breaker transitions to the Half...

Saga

The Saga pattern in distributed systems is akin to transactions in databases. It ensures that multiple operations across different services either succeed or fail together, maintaining consistency. Saga orchestrates this by associating each action with a compensatory action, which reverses the changes made by the action if any subsequent steps fail.

This functionality is crucial in avoiding inconsistent states in distributed environments. While similar to Software Transactional Memory (STM), Sagas specifically address distributed systems.

Consider the operation of a donut shop accepting delivery orders. The process involves several steps:

  1. Putting donuts into a box:
    • If subsequent steps fail, the donuts shouldn’t just be left in the box; they need to be unpacked and displayed again.
  2. Putting a label on the box:
    • If a failure occurs after this step, the label needs to be removed.
  3. Handing the...

Immutable data

At the very beginning of this book, back in Chapter 1, Getting Started with Kotlin, we mentioned how Kotlin favors immutability with the values over variables and immutable collections being the default. But the Arrow library takes this concept even further with its Optics module.

In order to work with the Optics library, we need to add the following dependency to our project:

implementation("io.arrow-kt:arrow-optics:1.2.1")

When discussing data classes in Chapter 2, Working with Creational Patterns, we mentioned that they have a copy method, which is undoubtedly useful for keeping immutable state but may be slightly cumbersome if we wanted to change deeply nested structures.

Let’s take the following example. We have a box of donuts that contains just a list of already familiar donuts:

data class DonutBoxOptics(val donuts: List<Donut>)
val donutBox = DonutBoxOptics(
    listOf(
        Donut("Gianduja Chocolate &...

Summary

This chapter has showcased practical applications of functional programming using the Arrow library, along with implementations of various patterns discussed in previous chapters. The Arrow library provides a powerful toolset for functional programming, enhancing Kotlin’s capabilities in handling immutability, concurrent state modifications, resilience, and data manipulation.

Arrow also offers a sophisticated approach to error handling with its support for typed errors and the Either data type. Typed errors in Arrow enhance the robustness of Kotlin programs by making potential errors explicit and manageable at compile time. This approach promotes proactive error handling, contrasting with Kotlin’s traditional exception handling, which can lead to unpredictable runtime errors and code that is more challenging to maintain.

The Either type in Arrow is particularly effective, representing a value as either a success type or an error type. This distinction...

Questions

  1. Explain the concept of typed errors in Arrow and how the Either data type enhances error handling in Kotlin programs.
  2. Describe the role of TVars in Arrow’s STM and explain how they differ from regular variables.
  3. In what scenarios would using Arrow’s Optics library be more beneficial than using traditional methods for modifying immutable data?

Learn more on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://discord.com/invite/xQ7vVN4XSc

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Kotlin Design Patterns and Best Practices - Third Edition
Published in: Apr 2024Publisher: PacktISBN-13: 9781805127765
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Alexey Soshin

Alexey Soshin is a software architect with 18 years of experience in the industry. He started exploring Kotlin when Kotlin was still in beta, and since then has been a big enthusiast of the language. He's a conference speaker, published writer, and the author of a video course titled Pragmatic System Design
Read more about Alexey Soshin