Educational Rust implemenation of Auction Sniper from Growing Object-Oriented Software, Guided By Tests

Overview

Auction Sniper

Educational Rust not-OOP implemenation of Auction Sniper from "Growing Object-Oriented Software, Guided By Tests" book

More about it in Data-oriented, clean&hexagonal architecture software in Rust – through an example project blog post.

Features:

  • Services (main application logical threads/actors) with graceful shutdown on demand or error
  • Simple Event-Log-based communication.
  • Ports from the Hexagonal Architecture with support for cross-port database transactions
You might also like...
Automatically generates Rust FFI bindings to C (and some C++) libraries.

bindgen bindgen automatically generates Rust FFI bindings to C (and some C++) libraries. For example, given the C header doggo.h: typedef struct Doggo

Safe interop between Rust and C++

CXX — safe FFI between Rust and C++ This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++, not subject to the m

Safe Rust bridge for creating Erlang NIF functions

Rustler Documentation | Getting Started | Example Rustler is a library for writing Erlang NIFs in safe Rust code. That means there should be no ways t

Bridge the gap between Haskell and Rust

Curryrs Curryrs (a play on the name of Haskell Curry, rs for Rust libraries, and it's pronunciation couriers) is a library for providing easy to use b

Rust in Haskell FFI Example

Provides an example for using Rust in Haskell. To use this you'll need cargo, rustc, cabal and GHC installed. To execute the example run the following

Run Java code from Rust!

Java Native Interface Bindings for Rust This library provides complete FFI bindings to the Java Native Interface, as well as a safe and intuitive wrap

Embedding Rust in Java

Java/Rust Example An example project showing how to call into Rust code from Java. OSX Linux Windows Requirements Java 7+ Rust (tested with 1.0, night

Rust-JDBC bindings

jdbc A Rust library that allows you to use JDBC and JDBC drivers. Usage First, add the following to your Cargo.toml: [dependencies] jdbc = "0.1" Next,

Lua 5.3 bindings for Rust

rust-lua53 Aims to be complete Rust bindings for Lua 5.3 and beyond. Currently, master is tracking Lua 5.3.3. Requires a Unix-like environment. On Win

Comments
  • replace types with dynamic dispatch and NVI

    replace types with dynamic dispatch and NVI

    Hi!

    Couldn't resist an opportunity to nerd out about software architecture! I must say I really enjoy the overall theme here! However, I can't say I like the concrete implementation as much. There are two pragmatic, and two philosophical reasons for that.

    The first pragmatic problem is that this is very complex code. Like the following line reads very much like trolling of the Rust language :)

    conn: &mut <<<Self as BiddingStateStore>::Persistence as persistence::Persistence>::Connection as persistence::Connection>::Transaction<'a>,
    

    The second pragmatic problem is that, because everything is generic, the build times, once this architecture is scaled up, are going to be atrocious -- there are no opportunities for separate compilation in this setup.

    The first philosophical problem is that everything being generic doesn't make sense. The actual business rules should be agnostic of the implementation of the persistence, and not parametrized by it. It doesn't make sense that we, essentially, get two copies of the application -- one for in-memory stuff, one for postgress stuff.

    The second philosophical problem is that there's little separation between the interface to plug the outside world into, and the interface we can rely on internally. Each extensibility point has two sides -- one for the provider of extension, and one for the consumer of the extension. The two interfaces often want to be a bit different, and it's nice to have a strict separation between the two. In the concrete terms, BiddingStateStore has two methods which needs to be implemented, and two convenience methods for users to call. But it is possible to override convenience methods, which doesn't sit right with me.

    Before proceeding with describing my own take on this, I want to quickly share a trick to reduce some syntactic repetition in the current impl. I think nested associated type lookups, like the one quoted, can be reduced by using type family pattern (not sure, haven't tried this refactor):

        trait Persistance {
            type Connection: Connection<Self>;
            type Transaction: Transaction<Self>;
        }
    
        trait Connection<P: Persistance> {
            fn start_transaction(&mut self) -> Result<P::Transaction>;
        }
    
        trait Transaction<P: Persistance> {
            fn commit(self: Box<Self>) -> Result<()>;
        }
    

    The idea here is to have one main type, which binds together all other types. Then you parametrize stuff over P: Persistence, and there's always one step to get any particular type.

    Anyway, that's not how I'd do it. The solution I like arises from the two philosophical points.

    First, we definitely want the stuff to be configurable, so we need to have some sort of trait Database to allow for that. At the same time, we don't want the bulk of the code to care about potentially different databases at all. We really want the meat of the code to just deal with a database, so we want struct Database. The way to code this might be like this:

    
    pub struct Database {
      imp: Box<dyn DatabaseImpl>
    }
    
    // This is the interface that most of our applications sees --
    // just a concrete type with a bunch of methods
    impl Database {
      pub fn query(&self, query: &str) -> QueryResult {
        self.imp.query(query)
      }
      pub fn query_foo(self) -> QueryResult {
        self.query("foo")
      }
    }
    
    // And this is the interface that `main` / `test` see -- 
    // that's how you actually plug a custom store
    pub trait DatabaseImpl {
      fn query(&self, query: &str) -> QueryResult;
    }
    
    impl<T: DatabaseImpl> From<T> for Database {
      fn from(imp: T) -> Database { 
        let imp = Box::new(imp);
        Database { imp }
      }
    }
    

    And what's the bulk of my PR is -- just ripping out type parameters and replacing them with concrete Persistance, Connection, Transaction types. The polymorphism is preserved though, because those concrete types store a polymorphic dyn thing inside. In a sense, that's just an appliation of non-virtual interface pattern to Rust.

    Pragmatically:

    • this simplifies the code a lot -- we don't need to be generic everywhere
    • this should improve compile times -- now most of the code is concrete, and can be compiled separatelly.

    Curious what you think about it!

    opened by matklad 8
Owner
Dawid Ciężarkiewicz
Born 1985, programming since ~1993, on Linux ~1998, professionally ~2003, on github ~2008, with Rust ~2013. Deep&wide generalist, FOSS enthusiast.
Dawid Ciężarkiewicz
Object Pool LockFree in Rust

Lock Free Object Pool A thread-safe object pool collection with automatic return. Some implementations are lockfree : LinearObjectPool SpinLockObjectP

Vaillant Etienne 30 Sep 7, 2022
Omi - Object Mapping Intelligently

Omi - Object Mapping Intelligently A library that maps object structures to relational databases and provides interfaces for common queries and update

Amphitheatre 6 Dec 28, 2022
Rust - Empowering everyone to build reliable and efficient software.

The Rust Programming Language This is the main source code repository for Rust. It contains the compiler, standard library, and documentation. Note: t

The Rust Programming Language 75.9k Dec 28, 2022
A newborn programming language for extensible software

A newborn programming language for extensible software.

Alexey Shmalko 17 Nov 29, 2022
Slitter is a C- and Rust-callable slab allocator implemented primarily in Rust, with some C for performance or to avoid unstable Rust features.

Slitter is a less footgunny slab allocator Slitter is a classically structured thread-caching slab allocator that's meant to help write reliable long-

Backtrace Labs 133 Dec 5, 2022
A Rust crate for automatically generating C header files from Rust source file.

Please be aware that this crate is no longer actively maintained, please look into the much more feature rich cbindgen instead. rusty-cheddar rusty-ch

Sean Marshallsay 190 Nov 12, 2022
Rust-ffi-guide - A guide for doing FFI using Rust

Using unsafe for Fun and Profit A guide to traversing the FFI boundary between Rust and other languages. A rendered version is available here. This gu

Michael Bryan 261 Dec 1, 2022
Rust library for build scripts to compile C/C++ code into a Rust library

A library to compile C/C++/assembly into a Rust library/application.

Alex Crichton 1.3k Dec 21, 2022
Rust based WASM/JS bindings for ur-rust

ur-wasm-js WASM/JS bindings for the ur-rust rust library Getting started Installation Either build the library yourself with wasm-pack or install for

Lightning Digital Entertainment 5 Feb 28, 2024
A project for generating C bindings from Rust code

cbindgen   Read the full user docs here! cbindgen creates C/C++11 headers for Rust libraries which expose a public C API. While you could do this by h

Ryan Hunt 1.7k Jan 3, 2023