Hacker News new | past | comments | ask | show | jobs | submit | Dowwie's comments login

Use dependency injection and mock behaviors. This technique works in several programming languages, including Rust.

Rust has modules, crates and workspaces. To optimize builds, you'll eventually move shared resources to their own crate(s).


I feel in rust you want to be a lot more judicious in where you introduce and deal with traits than in other languages with interfaces. Author blames lifetimes for this but I think the truth is that it is because there is no garbage collector so not everything is a fat pointer and fat pointers cannot have generic methods anyways because generic methods are monomorphized so they feel a bit lame even if you would reach for them.

Thus you almost certainly need parametric polymorphism whereas other languages described would use implementation/interface/inheritance/duck polymorphism. Parametric polymorphism explodes rapidly if you aren't judicious and it doesn't feel very agile.

Once you are dealing in traits, does that trait have a copy bound or am I going to need to take a borrow and also grab a lifetime next to my trait parameter? Or should I just hide it all by slapping my mock with an `impl Trait for Arc<RefCell<Mock>>` or equivalent?


Here's how I currently am doing it: I use the repository pattern. I use a trait:

  pub trait LibraryRepository: Send + Sync + 'static {
      async fn create_supplier(
          &self,
          request: supplier::CreateRequest,
      ) -> Result<Supplier, supplier::CreateError>;
I am splitting things "vertically" (aka by feature) rather than "horizontally" (aka by layer). So "library" is a feature of my app, and "suppliers" are a concept within that feature. This call ultimately takes the information in a CreateRequest and inserts it into a database.

My implementation looks something like this:

    impl LibraryRepository for Arc<Sqlite> {
        async fn create_supplier(
            &self,
            request: supplier::CreateRequest,
        ) -> Result<Supplier, supplier::CreateError> {
            let mut tx = self
                .pool
                .begin()
                .await
                .map_err(|e| anyhow!(e).context("failed to start SQLite transaction"))?;
    
            let name = request.name().clone();
    
            let supplier = self.create_supplier(&mut tx, request).await.map_err(|e| {
                anyhow!(e).context(format!("failed to save supplier with name {name:?}"))
            })?;
    
            tx.commit()
                .await
                .map_err(|e| anyhow!(e).context("failed to commit SQLite transaction"))?;
    
            Ok(supplier)
        }

where Sqlite is

  #[derive(Debug, Clone)]
  pub struct Sqlite {
      pool: sqlx::SqlitePool,
  }
You'll notice this basically:

  1. starts a transaction
  2. delegates to an inherent method with the same name
  3. finishes the transaction
The inherent method has this signature:

  impl Sqlite {
      async fn create_supplier(
          self: &Arc<Self>,
          tx: &mut Transaction<'_, sqlx::Sqlite>,
          request: supplier::CreateRequest,
      ) -> Result<Supplier, sqlx::Error> {
So, I can choose how I want to test: with a real database, or without.

If I want to write a test using a real database, I can do so, by testing the inherent method and passing it a transaction my test harness has prepared. sqlx makes this really nice.

If I'm testing some other function, and I want to mock the database, I create a mock implementation of LibraryService, and inject it there. Won't ever interact with the database at all.

In practice, my application is 95% end-to-end tests right now because a lot of it is CRUD with little logic, but the structure means that when I've wanted to do some more fine-grained tests, it's been trivial. The tradeoff is that there's a lot of boilerplate at the moment. I'm considering trying to reduce it, but I'm okay with it right now, as it's the kind that's pretty boring: the worst thing that's happened is me copy/pasting one of these implementations of a method and forgetting to change the message in that format!. I am also not 100% sure if I like using anyhow! here, as I think I'm erasing too much of the error context. But it's working well enough for now.

I got this idea from https://www.howtocodeit.com/articles/master-hexagonal-archit..., which I am very interested to see the final part of. (and also, I find the tone pretty annoying, but the ideas are good, and it's thorough.) I'm not 100% sure that I like every aspect of this specific implementation, but it's served me pretty well so far.


You've dodged the meat of my complaint by not having an example where you need to inject into a struct to test an implementation. Moreover, if you slap `Send + Sync + 'static` then you can certainly avoid the problems I am hinting at: you've committed to never having a lifetime and won't have to deal with the borrow checker.

Inject a short lived bee into your example. A database that is only going to live for a finite time.


> You've dodged the meat of my complaint by not having an example where you need to inject into a struct to test an implementation.

Sure. "Dr, it hurts... well stop doing that." Sometimes, you can design around the issue. I don't claim that this specific pattern works for everything, just that this is how my real-world application is built.

> Moreover, if you slap `Send + Sync + 'static` then you can certainly avoid the problems I am hinting at: you've committed to never having a lifetime and won't have to deal with the borrow checker.

Yes. Sometimes, one atomic increment on startup is worth not making your code more complex.

> A database that is only going to live for a finite time.

This is just not the case for a web application.


Thanks so much for the detailed example. Bookmarking for when I need to find it again...

No problem! I also left this response to someone on reddit who said similar:

Nice. I want to write about my experiences someday, but some quick random thoughts about this:

My repository files are huge. i need to break them up. More submodules can work, and defining the inherent methods in a different module than the trait implementation.

I've found the directory structure this advocates, that is,

    ├── src
    │   ├── domain
    │   ├── inbound
    │   ├── outbound
gets a bit weird when you're splitting things up by feature, because you end up re-doing the same directories inside of all three of the submodules. I want to see if moving to something more like

    ├── src
    │   ├── feature1
    │   │   ├── domain
    │   │   ├── inbound
    │   │   ├── outbound
    │   ├── feature2
    │   │   ├── domain
    │   │   ├── inbound
    │   │   ├── outbound
feels better. Which is of course its own kind of repetition, but I feel like if I'm splitting by feature, having each feature in its own directory with the repetition being the domain/inbound/outbound layer making more sense.

I'm also curious about if coherence will allow me to move this to each feature being its own crate. compile times aren't terrible right now, but as things grow... we'll see.


This model is a fucking beast. I am so excited about the opportunities this presents.


Is this a TLDraw wrapper?



This study needs to capture the affects of sleep deficiency. I'm in my mid-forties and don't sleep enough anymore (6-7 hours at best).


What matters is quantity of deep sleep and REM sleep.

REM sleep seems to be related to archiving of events( memory formation ) while lack of deep sleep affects the brain itself.

Pickup a smartwatch and track the sleep stages with Apple watch being the most accurate.


I'm not using Firefox because it's functionally superior to Chrome. I use it because it's not a surveillance technology and Chrome is. Privacy is the only reason why I'm using Firefox.

The board governing Firefox development has got to be some of the worst. They don't understand the product nor their users.


It can display logs in-context. Awesome!


The goal is to reduce government spending by $2 Trillion in 4 years. If you want to see how this is going: https://polymarket.com/doge


That's not the goal at all. And that's not how its going.

https://www.npr.org/2025/02/19/nx-s1-5302705/doge-overstates...


> Of the DOGE list's initial claim of $16 billion in savings, half came from an Immigration and Customs Enforcement (ICE) listing that was entered into the Federal Procurement Data System (FPDS) in 2022 with a whopping $8 billion maximum possible value.

> According to a DOGE post on X, that number was a typo that was corrected in the contract database to $8 million on Jan. 22 of this year before being terminated a week later, and DOGE "has always used the correct $8M in its calculations."

Jeez, that's pretty damning.


This linked website has an incentive to portray this "savings" as larger than it actually is.


what are your sources for news?


> what are your sources for news?

Unironically, my friends, family and colleagues. If anything truly important happens that ends up being relevant to me, the probability that one of them tells me is close to 100%. I don’t need the news or social media for that.


I can't imagine how this could possibly be used for amusement purposes


Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: