Created
February 14, 2019 23:10
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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