use hexser::prelude::*;
#[derive(HexDomain, HexEntity, Clone)]
struct Todo {
id: String,
title: String,
done: bool,
}
trait TodoRepository: Repository<Todo> {
fn find_active(&self) -> HexResult<Vec<Todo>>;
fn count_completed(&self) -> HexResult<usize>;
}
#[derive(HexAdapter)]
struct InMemoryTodoRepository {
todos: Vec<Todo>,
}
impl InMemoryTodoRepository {
fn new() -> Self {
Self { todos: Vec::new() }
}
fn with_todos(todos: Vec<Todo>) -> Self {
Self { todos }
}
}
impl Repository<Todo> for InMemoryTodoRepository {
fn save(&mut self, todo: Todo) -> HexResult<()> {
if let Some(existing) = self.todos.iter_mut().find(|t| t.id == todo.id) {
*existing = todo;
} else {
self.todos.push(todo);
}
Ok(())
}
}
#[derive(Clone)]
enum TodoFilter {
All,
ById(String),
}
#[derive(Clone, Copy)]
enum TodoSortKey {
Id,
}
impl hexser::ports::repository::QueryRepository<Todo> for InMemoryTodoRepository {
type Filter = TodoFilter;
type SortKey = TodoSortKey;
fn find_one(&self, filter: &TodoFilter) -> HexResult<Option<Todo>> {
let found = match filter {
TodoFilter::All => self.todos.first().cloned(),
TodoFilter::ById(id) => self.todos.iter().find(|t| &t.id == id).cloned(),
};
Ok(found)
}
fn find(
&self,
filter: &TodoFilter,
_opts: hexser::ports::repository::FindOptions<TodoSortKey>,
) -> HexResult<Vec<Todo>> {
let items: Vec<Todo> = match filter {
TodoFilter::All => self.todos.clone(),
TodoFilter::ById(id) => self.todos.iter().filter(|t| &t.id == id).cloned().collect(),
};
Ok(items)
}
fn delete_where(&mut self, filter: &TodoFilter) -> HexResult<u64> {
let before = self.todos.len();
match filter {
TodoFilter::All => self.todos.clear(),
TodoFilter::ById(id) => self.todos.retain(|t| &t.id != id),
}
Ok((before.saturating_sub(self.todos.len())) as u64)
}
}
impl TodoRepository for InMemoryTodoRepository {
fn find_active(&self) -> HexResult<Vec<Todo>> {
Ok(self.todos.iter().filter(|t| !t.done).cloned().collect())
}
fn count_completed(&self) -> HexResult<usize> {
Ok(self.todos.iter().filter(|t| t.done).count())
}
}
fn main() {
println!("Tutorial 03: Implementing Adapters\n");
println!("{}", "=".repeat(50));
let repo = InMemoryTodoRepository::with_todos(vec![
Todo {
id: String::from("1"),
title: String::from("Learn hexagonal architecture"),
done: false,
},
Todo {
id: String::from("2"),
title: String::from("Read documentation"),
done: true,
},
]);
println!("\nUsing adapter methods:");
let total = <InMemoryTodoRepository as hexser::ports::repository::QueryRepository<Todo>>::find(
&repo,
&TodoFilter::All,
hexser::ports::repository::FindOptions::default(),
)
.unwrap()
.len();
println!(" Total todos: {total}");
println!(" Active todos: {}", repo.find_active().unwrap().len());
println!(" Completed: {}", repo.count_completed().unwrap());
let graph = HexGraph::current();
println!("\nAll three layers registered:");
graph.pretty_print();
println!("\nArchitecture layers:");
println!(
" Domain: {} components",
graph.nodes_by_layer(Layer::Domain).len()
);
println!(
" Ports: {} components",
graph.nodes_by_layer(Layer::Port).len()
);
println!(
" Adapters: {} components",
graph.nodes_by_layer(Layer::Adapter).len()
);
println!("\nKey concepts:");
println!(" Adapters implement ports using specific technology");
println!(" InMemoryTodoRepository uses Vec (in-memory storage)");
println!(" Could swap with PostgresAdapter, SqliteAdapter, etc.");
println!(" Domain and ports remain unchanged");
println!("\nYou've completed the core hexagonal pattern!");
println!("{}", "=".repeat(50));
println!("\nNext: Try Tutorial 04 - Application Layer");
println!(" cargo run --example tutorial_04_application");
}