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
1,598 downloads per month
65KB
1.5K
SLoC
📐 Rust Arkitect
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