Blog Infos
Author
Published
Topics
Published
Topics

Photo by Tasha Jolley on Unsplash

 

There are many people who have shared their own versions of use case implementations, each considering it either an improvement or the right way to create a use case in Android.

These are excellent articles with valuable insights, and I highly recommend reading them to understand different perspectives on use case implementations.

Now, you might wonder, why do I need to create my own version? Well, the reason is simple. Everyone has their unique experiences and approaches to solving problems. By sharing my own version, I’m not invalidating others’ ideas. Instead, I’m offering my perspective on how I see the problem and the solution that works best for me. You might find it relatable or gain new insights from it, and that’s the purpose of sharing — to contribute to the collective knowledge and encourage diverse perspectives in the software engineering community.

This article aligns with the principles of the Fluent and Fun Clean Architecture Series, which I’ll be advocating. If you haven’t had the chance to read it yet, no worries — it’s not a prerequisite for understanding this article.However, feel free to explore the series for additional insights and context on the concepts discussed here, as we have already shared a high-level explanation of why I am recreating my usecase.

First, What is a UseCase?

In software development in general, a use case is a description of a specific action or interaction that a system or application performs to achieve a particular goal for an actor (user or another system). It outlines the “steps” and interactions between different components to demonstrate how a specific functionality is used in the system. Use cases are often used to capture “functional requirements” and define the behavior of the system from the perspective of its users.

In the context of Clean Architecture, the term “use case” refers to a specific architectural component that represents a business operation or task that the application needs to perform. It is a part of the domain layer and acts as an intermediary between the presentation layer (UI) and the domain layer. The use case is responsible for orchestrating the flow of data and business logic to achieve the desired outcome.

In summary, the general software development use case describes the functionality from the user’s perspective, while the Clean Architecture use case represents a specific business operation in the domain layer, adhering to the principles of separation of concerns and modularity.

So, What is a UseCase in “Fluent and Fun Clean Architecture”?

In Fluent and Fun Clean Architecture, a use case serves a more comprehensive purpose compared to traditional use cases. Instead of solely describing functionality from the user’s perspective, we approach it from the application’s viewpoint when executing an event or action.

This means that a use case not only outlines user actions but also represents the steps needed to achieve a specific result when an event is triggered. By combining these perspectives, we create expressive and readable use cases that align with the application’s requirements.

This expanded capability allows us to design a flexible system with encapsulated business logic, independent of the UI and data access. It enhances modularity and maintainability, making it easier to adapt and improve the software as needed. By focusing on the application’s perspective and detailing the steps for event handling, we gain a deeper understanding of the system’s behavior, leading to more efficient and adaptable software development.

How Do You Make a UseCase in Fluent and Fun Clean Architecture?

Before we dive into the UseCase of Fluent and Fun Clean Architecture, let’s first understand how to implement it in Clean Architecture.

For better understanding, Let’s consider a simple example of a use case in an e-commerce application:

Use CasePlace Order

ActionCustomer

Description: This use case represent the process of a customer placing an order for a product in the e-commerce application.

Requirements:

  1. The Customer should be logged in.
  2. The cart is not empty to be able to place an order.
  3. The amount of money in the wallet of the customer should not be less than the amount of the ordered item.
  4. If all three are met, update the product stock.
  5. Clear the cart in the app once placing an order is finished.
To implement it in Clean Architecture, you would typically follow a code structure similar to the one shown below.
class PlaceOrderUseCase(
    private val userRepository: UserRepository,
    private val productRepository: ProductRepository
) {
    operator fun invoke(order: Order) {
        if (userRepository.isLoggedIn()) {
            val cart = userRepository.getCart()
            if (cart.isNotEmpty()) {
                if (userRepository.hasEnoughFunds(order.getTotalPrice())) {
                    productRepository.updateProductStock(order)
                    userRepository.clearCart()
                } else {
                    throw InsufficientFundsException(
                        "Not enough funds in the wallet."
                    )
                }
            } else {
                throw EmptyCartException("The cart is empty.")
            }
        } else {
            throw NotLoggedInException("User is not logged in.")
        }
    }
}

 

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

Now, let’s break it down step by step to create a Use Case:

  1. Create a class with a name that consists of verb in present tense + noun/what (optional) + UseCase.
  2. Define the necessary parameters, which can be either Repositories or other Use Cases, depending on your application’s requirements.
  3. In Kotlin, you can make Use Case class instances callable as functions by defining the invoke() function with the operator modifier.

For further insights, you may refer to the official Android developer documentation, where you can explore how they define their domain layer and Use Cases, which shares similarities with our approach above.

In the context of Fluent and Fun Clean Architecture, the objective is to achieve a similar implementation as described below.
OrderUseCase.kt

fun OrderRepository.placeOrder(order: Order) {
    whenUserLoggedIn {
        whenCartNotEmpty {
            withSufficientFunds {
                updateProductStock(order)
                clearCart()
            }
        }
    }
}

The primary objective is to provide a clear and concise summary of the necessary steps when placing an order. In the following code snippet, you will notice that it reads like natural language, making the use case instantly understandable. Before placing an order, several checks must be completed.

Here’s the breakdown of the implementation:

  1. Instead of using a class, we’ve created a file named “OrderUseCase.”
  2. The file name follows the convention of noun/what (optional) + UseCase.
  3. There is no class involved; instead, we utilized a top-level extension function or a regular top-level function.
  4. The function itself represents the use case.
  5. The function’s name serves as the primary use case to be called from the presentation layer.
Why do I prefer a UseCase like this?
  1. Readability and Expressiveness: By using a chain of function calls with meaningful names (whenUserLoggedInwhenCartNotEmptywithSufficientFunds), the code becomes more readable and expressive. Each step of the process is clearly outlined, making it easier to understand the flow of the use case.
  2. Simplified Control Flow: The chain of function calls follows a natural flow of conditions that need to be met to place an order. This simplifies the control flow and makes the logic more straightforward.
  3. Modularity: The code separates concerns into different functions (whenUserLoggedInwhenCartNotEmptywithSufficientFunds), making each function handle a specific check or condition. This promotes modularity and makes it easier to change or add new checks without affecting the entire use case.
  4. Conciseness: The code avoids the need for a separate use case class with boilerplate code. It condenses the logic into a concise and focused extension function.
  5. Fluent Interface: The use of the when keyword in the function names (whenUserLoggedInwhenCartNotEmpty) gives the code a fluent interface style, enhancing readability and making it more similar to natural language.
  6. Clarity of Intent: The breakdown of the use case logic into separate functions allows the developer to clearly express their intent at each step of the process. It becomes evident what each condition represents in the context of placing an order.

Overall, we prefer this approach because it offers a more concise, expressive, and modular way of defining use case logic. It reduces boilerplate code and helps developers focus on the specific steps and conditions required to place an order. Additionally, it can be seen as an example of how functional programming concepts, such as chaining and fluent interfaces, can be applied to design more readable and expressive code.

Why would you not need a class for implementing a Use Case?

This is a valid question. Before providing an answer, let’s begin by defining what a class is in programming.

  • Classes are used to define blueprints for creating objects. You can create multiple instances (objects) of a class with different state and behavior.
  • Classes can have propertiesmember functions, and constructors, allowing you to create instances with varying initializations and states.
  • If you need to create multiple instances of a concept (e.g., multiple users, products, etc.), you would typically use a class.
What is state and behavior in class?
  • State represents the current data or properties of each object. It’s represented by instance variables (fields) within the class, and each object created from the class has its own set of these variables. For example, in the Car class, state attributes could include color, make, model, year, and fuel level, with each instance having its specific values.
class Car(private val color: String, 
          private val make: String, 
          private val model: String, 
          private val year: Int) {
    private var fuelLevel: Double = 0.0
}
  • Behavior in a class defines the actions or operations that objects can perform. It’s represented by methods within the class. These methods determine how objects interact with others and the outside world. For example, in the Car class, we might have methods like start(), accelerate(), brake(), and refuel() to represent the various behaviors a car object can exhibit based on its state.
class Car {
    private var fuelLevel: Double = 0.0

    // Behavior (methods)
    fun start() {
        println("Car has started.")
    }

    fun accelerate() {
        println("Car is accelerating.")
    }

    fun brake() {
        println("Car is braking.")
    }

    fun refuel(amount: Double) {
        fuelLevel += amount
        println("Car has been refueled with $amount gallons.")
    }
}
Answering the question:

As we can see, classes are indeed a powerful concept in programming. However, for certain use cases in Kotlin, we might not fully utilize the capabilities of a class if we use it only to invoke a single behavior or function.

In such scenarios, opting for a function instead of a class provides a more straightforward and concise approach. A function allows us to directly define the logic for the use case without introducing unnecessary class-related syntax and structure. It also aligns with functional programming principles, encouraging us to design use cases as pure functions, which are predictable, testable, and less prone to side effects.

To illustrate this point, let’s consider a simple analogy. Think of a Car as a class and walk as a function. If you only need to go to a place that is two to three minutes away, using a powerful Car with music, GPS, and air-conditioning might be overkill. In this case, you have the option to walk, which is a simpler and more direct approach.

Similarly, imagine a Phone as a class and talk as a function. If you want to say something to someone in the same room, using a powerful phone with various functionalities might not be necessary. You can opt to talk to that person directly, which is a more straightforward way to achieve your goal.

The key takeaway here is that there are different ways to accomplish tasks, and sometimes, a functional approach can be more suitable and efficient. By considering a functional approach first, we can focus on simplicity, readability, and expressive code. It is essential to choose the right tool for the job and leverage the appropriate concepts in software development. Embracing functional programming concepts in Kotlin can lead to cleaner and more maintainable code, ultimately enhancing the development experience.

Conclusion

In conclusion, in the context of Fluent and Fun Clean Architecture, we have explored a different approach to implementing use cases that offers advantages in terms of readability, expressiveness, and modularity. By leveraging top-level extension functions or regular top-level functions, we can create use cases that read like natural language, making the logic instantly understandable and easy to follow.

By favoring functions over classes for use cases, we simplify the control flow and avoid unnecessary boilerplate code, making the codebase more concise and focused. This functional approach aligns with the principles of Fluent and Fun Clean Architecture, promoting a more expressive and adaptable codebase.

However, it’s essential to note that the choice between using classes or functions for use cases depends on the specific requirements and complexity of the application. For more extensive use cases that involve state management and multiple instances, classes can still be a valuable choice.

This article only scratches the surface of the different possibilities for recreating use cases. In future articles, we will explore additional techniques and provide more examples of creative use case implementations.

Stay tuned for more exciting insights and examples of how Fluent and Fun Clean Architecture can transform the way we approach use cases and software development as a whole.

Photo by Aron Visuals on Unsplash

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Use cases show us the intent of the software. They describe the behavior of…
READ MORE
blog
Now, let’s delve deeper into the process of making our use cases more fluent…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu