Skip to content

Instantly share code, notes, and snippets.

@cfsamson
Created February 14, 2019 23:10
Show Gist options
  • Save cfsamson/99899b4e698a28390f2fce7a532d54a9 to your computer and use it in GitHub Desktop.
Save cfsamson/99899b4e698a28390f2fce7a532d54a9 to your computer and use it in GitHub Desktop.
A small code example on an idea of how to implement the Unit Of Work pattern in Rust
fn main() {
let mut session = UoW::new();
let entry = session.load::<SomeEntity>();
println!("{:?}", entry);
entry.value().msg = "Changed".to_string();
println!("{:?}", entry);
let other_entry = session.load::<SomeOtherEntity>();
println!("{:?}", other_entry);
// this also works
let other_entry = other_entry.value();
other_entry.msg = "Other has also changed".to_string();
other_entry.age += 1;
println!("{:?}", other_entry);
session.commit();
}
use std::any::{Any, TypeId};
use std::marker::PhantomData;
#[derive(Debug)]
struct UoW {
tracked: Vec<(TypeId, Box<Any>)>,
}
impl<'a> UoW {
fn new() -> Self {
UoW { tracked: vec![] }
}
fn load<T: Any + Entity + 'a>(&mut self) -> &mut Tracked<T> {
let t = TypeId::of::<T>();
let index = self.tracked.len();
// Let's pretend we get from the database
let repo = SomeRepo::new();
let entry: T = repo.get_by_id(1);
// If we load a new entity we should set the state to State::Added
self.tracked
.push((t, Box::new(Tracked::new(index, entry, State::Unhcanged))));
let y = &mut self.tracked[index].1;
// should be safe to unwrap since we know it's of type T
y.downcast_mut::<Tracked<T>>().unwrap()
}
fn commit(&mut self) {
let _some_entity_type = TypeId::of::<SomeEntity>();
let _some_other_entity_type = TypeId::of::<SomeOtherEntity>();
// -------- START TRANSACTION --------
for (key, entry) in self.tracked.drain(..) {
if key == _some_entity_type {
// still, we know the type so unwrapping should be safe enough for now...
let tracked = entry.downcast_ref::<Tracked<SomeEntity>>().unwrap();
println!(
"The frist entry will be \"{}\" db: \n{:?}",
if *tracked.state() == State::Added {
"added to the"
} else if *tracked.state() == State::Dirty {
"updated in the"
} else {
"unchanged in the"
},
tracked
);
} else if key == _some_other_entity_type {
// still, we know the type so unwrapping should be safe enough for now...
let tracked = entry.downcast_ref::<Tracked<SomeOtherEntity>>().unwrap();
println!(
"The other entry will be \"{}\" db: \n{:?}",
if *tracked.state() == State::Added {
"added to the"
} else if *tracked.state() == State::Dirty {
"updated in the"
} else {
"unchanged in the"
},
tracked
);
}
}
// ------- COMMIT TRANSACTION --------
}
}
#[derive(Debug)]
struct Tracked<T> {
id: usize,
inner: T,
state: State,
}
impl<T> Tracked<T> {
fn new(id: usize, inner: T, state: State) -> Self {
Tracked { id, inner, state }
}
fn value(&mut self) -> &mut T {
// for simplicity sake lets mark it as dirty if there has been a mutable borrow. Unless
// its already marked as Added when the entity doesn't exist in db yet anyway
if self.state != State::Added {
self.state = State::Dirty;
}
&mut self.inner
}
fn state(&self) -> &State {
&self.state
}
}
#[derive(Debug)]
struct SomeRepo<T> {
_marker: PhantomData<T>,
}
impl<T: Entity> SomeRepo<T> {
fn new() -> Self {
SomeRepo {
_marker: PhantomData,
}
}
fn get_by_id(&self, id: i32) -> T {
// get from db
T::new(id)
}
}
trait Entity {
fn new(id: i32) -> Self;
}
#[derive(Debug, PartialEq)]
enum State {
Added,
Dirty,
Unhcanged,
//...
}
#[derive(Debug)]
struct SomeEntity {
pub id: i32,
pub msg: String,
}
impl Entity for SomeEntity {
fn new(id: i32) -> Self {
SomeEntity {
id,
msg: String::from("Hello"),
}
}
}
#[derive(Debug)]
struct SomeOtherEntity {
pub id: i32,
pub msg: String,
pub age: u32,
}
impl Entity for SomeOtherEntity {
fn new(id: i32) -> Self {
SomeOtherEntity {
id,
msg: String::from("Other hello"),
age: 37,
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment