Skip to content
Arciphant
Esc
↑↓navigate↵open⌘Jpreview
On this page

Component layouts

The project layout and the source set layout — how Arciphant maps components to Gradle.

Arciphant supports two ways of mapping components to Gradle:

Choosing a layout

Both layouts enforce the same component structure and dependencies — they differ in how the structure is mapped to Gradle:

Project component layout Source set component layout
Gradle projects one per component one per functional module
Gradle plugins per component yes — registered in the DSL, see Using Plugins no — plugins are applied per module project
Artifacts one jar per component, with qualified names no jars — consumers use the compiled component classes
Tests and test fixtures standard Gradle test source set; test fixtures via the optional java-test-fixtures plugin Arciphant creates test and test-fixtures source sets per component
Configuration cache supported supported
Isolated projects (incubating) not (yet) supported supported

Project layout

This is the default layout, so no configuration is needed. To select it explicitly, use projectSetComponentLayout():

arciphant {
    projectSetComponentLayout()

    val template = template()
        .createComponent(name = "domain")
        .createComponent(name = "application", apiDependencies = setOf("domain"))

    module(name = "orders", template = template)
}

This creates the Gradle project :orders with subprojects :orders:domain and :orders:application:

orders
domain
src
main
test
testFixtures
application
src
main
test
testFixtures

src/testFixtures is only present once the optional java-test-fixtures plugin is applied — see Structure test code.

No Arciphant-specific constructs are involved: modules and components are plain Gradle projects with the standard source sets, and component dependencies are ordinary implementation and api project dependencies.

Source set layout

Enable the source set layout with sourceSetComponentLayout():

arciphant {
    sourceSetComponentLayout()

    val template = template()
        .createComponent(name = "domain")
        .createComponent(name = "application", apiDependencies = setOf("domain"))

    module(name = "orders", template = template)
}

This creates the Gradle project :orders with the following source sets by default:

orders
src
domain
domainTest
domainTestFixtures
application
applicationTest
applicationTestFixtures

Other projects consume a component through the compiled classes of its source set, not through a jar — the module’s jar stays empty. Applications packaged with Spring Boot’s bootJar still contain all component classes.

How test and test fixtures source sets are wired

A component’s test fixtures depend on its production source set; its tests depend on its test fixtures. Component dependencies are mirrored between the corresponding test-fixtures source sets, while test source sets never depend on other components’ test source sets.

The source set layout does not require the java-test-fixtures plugin. Arciphant also registers one Test task per component test source set, named exactly like that source set, and attaches it to the module’s standard test task. If the idea plugin is applied, test and test-fixtures directories are marked as test sources in IntelliJ IDEA.

Test source set configuration

Tests and test fixtures are enabled by default. Their names and global availability can be configured with source-set-specific settings:

arciphant {
    sourceSetComponentLayout()

    withTestSourceSet(true)
    withTestFixturesSourceSet(false)
    testSourceSetName { componentName -> "${componentName}Spec" }
    testFixturesSourceSetName { componentName -> "${componentName}Fixtures" }

    module(name = "orders")
        .createComponent(
            name = "domain",
            withTestSourceSet = false,
            withTestFixturesSourceSet = true,
        )
}

The component parameters override the global flags.

Source-set settings and component source-set options are rejected in the project layout rather than being silently ignored.

Customizing source directories

The component source sets can be customized per module project — in its build.gradle.kts or in a convention plugin. Arciphant registers two extensions in every functional moduleFunctional moduleDomain and library modules — modules that consist of components. Bundle modules are not functional modules.Concepts project to do so:

Extension Description
customizeAllComponents Customize source sets of all components
customizeComponent Customize source sets of one particular component

Both extensions provide the following methods, each taking a lambda that customizes the respective source set:

Method Description
productionSourceSet Customize the production source set
testSourceSet Customize the test source set
testFixturesSourceSet Customize the test fixtures source set

In customizeAllComponents, the lambda receives the SourceSet and the component name ({ sourceSet, componentName -> … }); in customizeComponent, it receives only the SourceSet ({ sourceSet -> … }).

Example:

// all components of the module
customizeAllComponents {
    productionSourceSet { sourceSet, componentName ->
        sourceSet.java.setSrcDirs(listOf("$componentName/java"))
        sourceSet.resources.setSrcDirs(listOf("$componentName/resources"))
    }
    testSourceSet { sourceSet, componentName ->
        sourceSet.java.setSrcDirs(listOf("$componentName/test/java"))
    }
    testFixturesSourceSet { sourceSet, componentName ->
        sourceSet.java.setSrcDirs(listOf("$componentName/testFixtures/java"))
    }
}

// a single component, selected by name
customizeComponent("domain") {
    productionSourceSet { sourceSet ->
        sourceSet.java.setSrcDirs(listOf("domain/java"))
    }
}

Use componentName to derive directories: for test and test-fixtures source sets it differs from the source set name (component domain, source set domainTest). Inside customizeComponent, the component name is available as the componentName property.

The example examples/source-set-customization in the Arciphant repository shows a complete minimal setup: one module with three components, using customizeAllComponents for all of them and customizeComponent for one.

Shared dependency configurations

External dependencies can be declared once for all component source sets of the same kind. In every Arciphant module project, each component configuration extends the corresponding standard configuration:

  • implementation, compileOnly and runtimeOnly reach all production source sets.
  • testImplementation, testCompileOnly and testRuntimeOnly reach all test source sets.
  • testFixturesImplementation, testFixturesCompileOnly and testFixturesRuntimeOnly (created by Arciphant) reach all test fixtures source sets.
  • With the java-library plugin applied, api reaches all production source sets and testFixturesApi (created by Arciphant) all test fixtures source sets.
dependencies {
    implementation("org.springframework:spring-context")
    "testFixturesApi"("org.assertj:assertj-core:3.27.0")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

This also works from a convention plugin, which replaces per-component convention plugins in this layout. Prefer applying such a convention plugin in the build.gradle.kts of each module project.

Restrictions

  • Component plugins cannot be configured in the source set layout because Gradle plugins can only be applied to projects. Configure the JVM and convention plugins on the module projects instead.
  • disableQualifiedArchiveBaseName() is also invalid in the source set layout: components produce no archives of their own, so component-qualified archive names do not apply.

Component dependencies in build scripts

A component of another module can be referenced by the module and component names of the Arciphant configuration, directly in the dependencies block — in the same style as external dependencies. The component extension registered by Arciphant creates the dependency notation; it is available in both component layouts. Arciphant resolves the target Gradle project path from the module configuration, so no project path has to be spelled out, and completes the dependency automatically:

dependencies {
    "implementation"(component(module = "contracts", component = "api"))
}

The notation is an ordinary project dependency, so compile and runtime classpath resolve through Gradle’s variant selection. Once the java-test-fixtures plugin is applied, the matching test-fixtures dependency is added automatically.

dependencies {
    "applicationImplementation"(component(module = "contracts", component = "api"))
}

The notation is a dependency on the target component’s …ApiElements configuration. Arciphant adds the matching runtime dependency to the source set’s runtimeOnly configuration and, if both the source and the target component have a test-fixtures source set, the dependency between the test-fixtures source sets as well.

Demo projects

The Arciphant repository contains the same demo application implemented in both layouts, so you can compare them side by side: arciphant-project-demo uses the project layout, arciphant-source-set-demo the source set layout — see Demo projects.

Was this page helpful?