Enforce directory structure with the Layout system
Defining where files may be placed
Section titled “Defining where files may be placed”layout { } defines where the files for a Role may be placed.
architecture { "UseCase" { // A UseCase may only be placed as *UseCase.kt inside useCase/ layout { "src/main/kotlin/com/example/useCase" / "*UseCase".ktFile() } }}Styles for writing directory declarations
Section titled “Styles for writing directory declarations”There are a few ways to write a directory declaration. You can use whichever style you prefer.
Chaining with slashes
Section titled “Chaining with slashes”When each level has only one thing in it, you can chain them with / on a single line.
layout { "src/main/kotlin/com/example/useCase" / "*UseCase".ktFile()}Wrapping in a block
Section titled “Wrapping in a block”When you want to place multiple things in the same directory, wrap them in a block.
layout { "src/main/kotlin/com/example/useCase" { "Get*UseCase".ktFile() "Update*UseCase".ktFile() }}Inside the block, you can write description. It becomes the explanation of “what this location is for” in the generated documentation.
layout { "src/main/kotlin/com/example/useCase" { description = "Used from multiple screens" "*UseCase".ktFile() }}Allowing arbitrary files to be placed
Section titled “Allowing arbitrary files to be placed”anyFile() — allow only files directly inside
Section titled “anyFile() — allow only files directly inside”Allows any file to be placed, but only directly inside that directory.
layout { "generated" { anyFile() }}In this example, subdirectories aren’t allowed. A file one level deeper is still reported as a violation.
ignore() — stop checking everything below
Section titled “ignore() — stop checking everything below”Stops checking everything below that directory, at any depth.
layout { "build".ignore()}You can also write it inside a block as "build" { ignore() }.
Naming a wildcard
Section titled “Naming a wildcard”capture(), which names a wildcard in a destination, is used together with templates. For checking layout alone you don’t need capture(); a plain * is fine. See Generating code from a template for how to write it.
Best practices
Section titled “Best practices”Writing out every directory for every Role is inefficient. Extract them into constants at a meaningful unit.
val appPackageDir = "src/main/kotlin/com/example"
architecture { "UseCase" { layout { appPackageDir / "useCase" / "*UseCase".ktFile() } }}Summary
Section titled “Summary”layout { }declares where a Role’s files may appear. A file in an undeclared location is a violation.anyFile()andignore()let you allow files of any kind to be placed, but they are escape hatches from deny by default. Use them with care.capture(), which names a wildcard in a destination, is used together with templates (Generating code from a template). For checking layout alone, a plain*is fine.
val appPackageDir = "src/main/kotlin/com/example" // paths can be pulled out into constants
architecture { "UseCase" { layout { appPackageDir / "useCase" / "*UseCase".ktFile() // join with "/" to declare where files go appPackageDir { // wrapping in a block declares the same thing "legacy" / "*UseCase".ktFile() } } }
"GeneratedFile" { summary = "Files generated by a tool" layout { "generated" { anyFile() } // any file directly inside is allowed (no subdirectories) } }
"BuildOutput" { summary = "Build output" layout { "build".ignore() // nothing below is checked } }}Next steps
Section titled “Next steps”- Role … Learn how to split a definition into files and functions as it grows.
- Konsist integration … Learn how to constrain not just placement but also the contents of files.
- Your first architecture definition … Introduce katachi into your project and actually write a declaration.