#architecture #rules #architectural #projects #define #fitness-functions #arkitect

rust_arkitect

rust_arkitect is a lightweight library for defining and validating architectural rules in Rust projects

19 unstable releases (3 breaking)

new 0.3.3 Jan 20, 2025
0.3.2 Jan 19, 2025
0.2.5 Jan 17, 2025
0.1.2 Jan 16, 2025
0.0.12 Dec 31, 2024

#329 in Development tools

Download history 236/week @ 2024-12-30 457/week @ 2025-01-06 901/week @ 2025-01-13

1,598 downloads per month

MIT license

65KB
1.5K SLoC

📐 Rust Arkitect

codecov Crate Version Crate Downloads Crate License

Rust Arkitect is a powerful tool that puts your architectural rules to the test, ensuring your Rust projects maintain a clean and modular structure. Inspired by phparkitect/arkitect, it provides a developer-friendly DSL to define and validate architectural constraints seamlessly integrated into your workflow.

🚀 Why Rust Arkitect?

Rust Arkitect helps you:

  • Define Rules Clearly: Use a simple DSL to specify architectural rules that mirror natural language
  • Refactor Legacy Code Safely: Establish a baseline of violations, monitor improvements, and prevent regressions
  • Validate Continuously: Integrate architectural tests seamlessly into your test suite, with immediate feedback during development

🧑‍💻 Getting Started

Add Rust Arkitect to your Cargo.toml:

[dev-dependencies]
rust_arkitect = "0.3"

Define your architectural rules:

use rust_arkitect::dsl::{ArchitecturalRules, Arkitect, Project};

#[test]
fn test_architectural_rules() {
    let project = Project::from_current_crate();

    #[rustfmt::skip]
    let rules = ArchitecturalRules::define()
        .rules_for_module("sample_project::application")
            .it_may_depend_on(&["sample_project::domain"])

        .rules_for_module("sample_project::domain")
            .it_must_not_depend_on_anything()

        .rules_for_module("sample_project::infrastructure")
            .it_may_depend_on(&["sample_project::domain", "sample_project::application"])

        .build();

    let result = Arkitect::ensure_that(project).complies_with(rules);

    assert!(result.is_ok(), "Detected {} violations", result.err().unwrap().len());
}

🏗️ Refactoring Legacy Code with Rust Arkitect

Rust Arkitect enables structured refactoring of legacy codebases. By establishing a baseline of current architectural violations, you can track improvements over time and ensure that no new violations are introduced during refactoring.

Example

Rust Arkitect enables structured refactoring of legacy codebases. By establishing a baseline of current architectural violations, you can track improvements over time and ensure that no new violations are introduced during refactoring.

Given a project with the following structure:

src/
├── application/
│   ├── mod.rs
│   └── service.rs
├── domain/
│   ├── mod.rs
│   ├── service.rs
│   └── entity.rs
├── infrastructure/
│   ├── mod.rs
│   ├── auth.rs
│   └── database.rs

You can define and test architectural rules:

#[test]
fn test_architecture_baseline() {
    let project = Project::from_current_workspace();

    #[rustfmt::skip]
    let rules = ArchitecturalRules::define()
        .rules_for_crate("application")
            .it_may_depend_on(&["domain"])

        .rules_for_module("domain")
            .it_must_not_depend_on_anything()

        .rules_for_module("infrastructure")
            .it_may_depend_on(&["domain", "application"])

        .build();

    let baseline_violations = 30;
    let result = Arkitect::ensure_that(project)
        .with_baseline(baseline_violations)
        .complies_with(rules);

    assert!(
        result.is_ok(),
        "Violations increased! Expected at most {}, found {}.",
        baseline_violations,
        current_violations
    );
}

This test ensures that the number of violations does not exceed the established baseline, promoting continuous improvement in your codebase's architecture.

🔍 Logging Violations

Rust Arkitect includes logging support to provide detailed information during the validation process. To enable logging, simply call Arkitect::init_logger() at the start of your tests. For example:

use rust_arkitect::dsl::{ArchitecturalRules, Arkitect, Project};

#[test]
fn test_logging_in_architecture_rules() {
    // Initialize logging
    Arkitect::init_logger();

    ...

    assert!(result.is_ok());
}

You can adjust the verbosity of the logging output by setting the RUST_LOG environment variable:

RUST_LOG=error cargo test -- --nocapture

Example Output:

[2024-12-30T12:17:08Z ERROR rust_arkitect::dsl] 🟥 Rule my_project::event_sourcing may depend on [std::fmt] violated: forbidden dependencies to [my_project::domain::events::event] in file:///users/random/projects/acme_project/src/event_sourcing/events.rs
[2024-12-30T12:17:08Z ERROR rust_arkitect::dsl] 🟥 Rule my_project::utils may not depend on any modules violated: forbidden dependencies to [my_project::infrastructure::redis::*] in file:///users/random/projects/acme_project/src/utils/refill.rs

🧙‍♂️ Custom Rules

Rust Arkitect allows you to create custom rules to test your project's architecture. These rules can be implemented by creating a struct and implementing the Rule trait for it. Below is an example of how to define and use a custom rule in a test:

// Define a custom rule
struct TestRule;

impl TestRule {
    fn new(_subject: &str, _dependencies: &[&str; 1]) -> TestRule {
        Self {}
    }
}

// Implement Display for the rule for better readability in logs
impl Display for TestRule {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "TestRule")
    }
}

// Implement the Rule trait
impl Rule for TestRule {
    fn apply(&self, _file: &RustFile) -> Result<(), String> {
        Ok(())
    }

    fn is_applicable(&self, _file: &RustFile) -> bool {
        true
    }
}

#[test]
fn test_custom_rule_execution() {
    // Define the project path
    let project = Project::new();

    // Create a new instance of the custom rule
    let rule = Box::new(TestRule::new("my_crate", &["another_crate::a_module"]));

    // Apply the rule to the project
    let result = Arkitect::ensure_that(project).complies_with(vec![rule]);

    // Assert that the rule passed
    assert!(result.is_ok());
}

😇 Built with Its Own Rules

Rust Arkitect is built and tested using the same architectural rules it enforces. This ensures the tool remains consistent with the principles it promotes. You can explore the architecture tests here to see it in action.

🛠️ Contribute

Rust Arkitect is an evolving project, and your feedback is invaluable. Whether you have suggestions, encounter issues, or wish to contribute, please open an issue or submit a pull request. Together, we can build robust and maintainable Rust applications.

Dependencies

~0.8–1.5MB
~29K SLoC