Blog Infos
Author
Published
Topics
,
Published

Konsist is a brand-new tool that helps with codebase standardization. It is compatible with various Kotlin projects including Android projects, Spring projects, and Kotlin Multiplatform projects.

Read Introduction to Konsist

Before diving into Konsist let’s briefly discuss the layered architecture. Components within the layered architecture pattern are organised into layers, each layer performing a specific role within the application. Each layer also defines the set of boundaries — classes and interfaces are only accessed by other classes and interfaces from desired layers.

Konsist can be used to automate the verification of these layers improving application code base quality and making it much more manageable. Let’s consider a few architectures and look at how we can guard their boundaries.

Challenges with Enforcing Architectural Boundaries

Imagine the application has a simple 4-layer architecture:

Here are the constraints derived from the above diagram:

  • presentation layer should only access entities defined in business layer
  • business layer should only access entities defined in presentation layer and persistence layer
  • persistence layer should only access entities defined in business layer and database layer
  • database layer should only access entities defined in persistence layer

Often these layers are represented as a separate package inside the Kotlin project:

All declarations (classes, interfaces, functions, etc.) defined in a given Kotlin module can be freely accessed by other declarations defined in this module — Kotlin compiler does not provide any way to define architectural boundaries when declarations exist inside a single feature module.

You may wonder why not just simply use gradle multi module to achieve the same result? The “Layer-driven modules” vs. “Feature-driven modules” are two different approaches to modularizing software. Each heaving own set of advantages and disadvantages.

Developers can easily write code that violates the intended architectural boundaries e.g. class defined in presentation layer is directly using the interface defined in the persistence layer skipping the business layer:

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

Jobs

Kotlin compiler will compile the code and the application will run just fine, but the architecture is broken.

It is common for developers to break architectural boundaries. Some developers may not be familiar with project architecture, others will simply make an honest mistake. This architectural violation can be obviously caught during PR review, but there is no such guarantee and often it is missed. In fact, this may be the main reason why many projects have broken architecture heaving spaghetti code. Let’s look at how Konsist can help to avoid such mistakes.

Protect 4 Tier Architecture

Konsist helps with maintaining architectural layer boundaries when multiple application layers are defined (as packages) within a single (feature) module.

Konsist can check whether each application layer uses resources only defined within permitted layers.

Firstly we need to define scope from the entire project (or the module) and use assertArchitecture method:

Konsist
    .scopeFromProject()
    .assertArchitecture {
    }

Then we need to define all layers represented by individual packages:

Konsist
    .scopeFromProject()
    .assertArchitecture {
        val presentation = Layer("Presentation", "com.myapp.presentation..")
        val business = Layer("Business", "com.myapp.business..")
        val persistence = Layer("Persistence", "com.myapp.persistence..")
        val database = Layer("Database", "com.myapp.database..")
    }

Then we have to define dependencies between layers:

Konsist
    .scopeFromProject()
    .assertArchitecture {
        val presentation = Layer("Presentation", "com.myapp.presentation..")
        val business = Layer("Business", "com.myapp.business..")
        val persistence = Layer("Persistence", "com.myapp.persistence..")
        val database = Layer("Database", "com.myapp.database..")

        presentation.dependsOn(business)
        business.dependsOn(presentation)
        business.dependsOn(persistence)
        persistence.dependsOn(business)
        business.dependsOn(database)
        database.dependsOn(business)
    }

Finally, we have to wrap Konsist guard inside a unit test:

@Test
fun `architecture has correct dependencies`() {
    Konsist
        .scopeFromProject()
        .assertArchitecture {
            // Define layers
            val presentation = Layer("Presentation", "com.myapp.presentation..")
            val business = Layer("Business", "com.myapp.business..")
            val persistence = Layer("Persistence", "com.myapp.persistence..")
            val database = Layer("Database", "com.myapp.database..")

            // Define layer dependencies
            presentation.dependsOn(business)
            business.dependsOn(presentation)
            business.dependsOn(persistence)
            persistence.dependsOn(business)
            business.dependsOn(database)
            database.dependsOn(business)
        }
}

The above test will verify dependencies between architectural layers. This test should be executed as part of PR verification to provide a solid architectural check.

Protect Clean Architecture

The Clean Architecture is another popular architecture. One of the key concepts in this architecture is the dependency rule, which stipulates that dependencies should only point inward toward the core business logic:

In this simple 3-layer Clean Architecture, the innermost circle is the domain layer, containing core business logic. Surrounding it are the presentation and data layers, which handle user interfaces and data storage respectively. Dependencies flow inward, ensuring the domain layer remains isolated from external changes.

Here are the constraints derived from the above diagram:

  • domain layer is independent
  • presentation layer depends on the domain layer
  • data layer depends on the domain layer

To projects such architecture using Konsist we need to define a similar test. This time we will define different dependencies between layers:

@Test
fun `clean architecture layers have correct dependencies`() {
    Konsist
        .scopeFromProduction()
        .assertArchitecture {
            // Define layers
            val domain = Layer("Domain", "com.myapp.domain..")
            val presentation = Layer("Presentation", "com.myapp.presentation..")
            val data = Layer("Data", "com.myapp.data..")

            // Define layer dependencies
            domain.dependsOnNothing()
            presentation.dependsOn(domain)
            data.dependsOn(domain)
        }
}
Summary

Konsist is a tool designed to ensure architectural consistency in Kotlin projects, including Android, Spring, and Kotlin Multiplatform. It verifies architectural layer boundaries, addressing common pitfalls where developers might inadvertently breach these structures. Without such checks, architectural violations might go unnoticed in PR reviews, leading to problematic code structures. By embedding Konsist’s checks in unit tests, projects can maintain and enforce their intended architecture during PR verification, benefiting both standard and Clean Architecture paradigms.

Follow me on Twitter.

Links

 

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Drag and Drop reordering in Recyclerview can be achieved with ItemTouchHelper (checkout implementation reference).…
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