actix / actix-web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.
https://actix.rs
Apache License 2.0
21.6k stars 1.67k forks source link

Support for handlers with different DAOs #832

Closed sazzer closed 5 years ago

sazzer commented 5 years ago

I'm new to Rust, so forgive me if this is obvious to more seasoned devs. This is also really a question on how to do things rather than a problem with Actix, so sorry if this is the wrong way to address this.

I'm playing with Actix Web, and want to have multiple handlers in the same server that do different things. (Monolith application). I'm also wanting to have a clean design, and specifically to ensure that code can only access what it needs to without over-exposing things.

So, for example, I might have:

I would then want to have

I can't see an easy way to do this.

I can use App.data but then every handler has access to every DAO. Not the biggest problem, but it is a shame. I'm also not sure if that would work with trait objects, so that my handlers can depend on Traits rather than Implementations. (Spot the Java Developer :) )

I've also tried building closures for my handlers, so I call a function that is passed the DAO and it returns the handler function. This seems like it should work but I got into a huge mess with the borrow checker.

None of the examples that I've seen address this particular use case. They all seem to just assume that the handler can access everything.

Cheers

sazzer commented 5 years ago

Update. I can use App.data() with trait objects, but I need to register the object once per trait.

This doesn't work:

fn index(userDao: web::Data<Arc<UserRetriever>>) -> String {
  userDao.get_user_name()
}

pub fn start_service() -> std::io::Result<()> {
  let userDao = Arc::new(UserDao{});

  HttpServer::new(move || {
      App::new()
        .data(userDao.clone())
        // enable logger
        .wrap(middleware::Logger::default())
        .service(web::resource("/").to(index))
  })
  .bind("127.0.0.1:8080")?
  .run()
}

Whereas this does work:

fn index(userDao: web::Data<Arc<UserRetriever>>) -> String {
  userDao.get_user_name()
}

pub fn start_service() -> std::io::Result<()> {
  let userDao = Arc::new(UserDao{});

  HttpServer::new(move || {
      App::new()
        // UserRetriever is a trait that UserDao implements that has the read-only methods on it
        .data::<Arc<UserRetriever>>(userDao.clone())
        // UserModifier is a trait that UserDao implements that has the write-only methods on it
        .data::<Arc<UserModifier>>(userDao.clone())
        // UserService is UserRetriever + UserModifier
        .data::<Arc<UserService>>(userDao.clone())
        // enable logger
        .wrap(middleware::Logger::default())
        .service(web::resource("/").to(index))
  })
  .bind("127.0.0.1:8080")?
  .run()
}
fafhrd91 commented 5 years ago

you can set data on resource and scope levels

https://docs.rs/actix-web/1.0.0-beta.5/actix_web/struct.Resource.html#method.data https://docs.rs/actix-web/1.0.0-beta.5/actix_web/struct.Scope.html#method.data