diff --git a/Cargo.lock b/Cargo.lock index c6bcff7..202028d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,6 +613,7 @@ name = "domain" version = "0.0.1" dependencies = [ "async-trait", + "bson", "serde", "serde_derive", ] diff --git a/app/src/endpoints.rs b/app/src/endpoints.rs index 800f0b1..d9d901c 100644 --- a/app/src/endpoints.rs +++ b/app/src/endpoints.rs @@ -21,7 +21,7 @@ pub async fn get_user( path: Path, ) -> HttpResponse { let query = GetUserQuery { - id: path.to_string(), + id: path.into_inner(), }; let option = handler.handle(query).await; @@ -30,7 +30,7 @@ pub async fn get_user( Some(user) => HttpResponse::Ok().json(UserResponse { name: user.name, age: user.age, - id: path.to_string(), + id: user.id, }), None => HttpResponse::BadRequest().json(ErrorResponse { code: 101 }), } @@ -49,10 +49,12 @@ pub async fn get_user( #[post("/user")] pub async fn create_user( handler: Data>>, - request: Json, + json: Json, ) -> HttpResponse { + let request = json.into_inner(); + let command = CreateUserCommand { - name: request.name.to_string(), + name: request.name, age: request.age, }; diff --git a/docker-compose.yml b/docker-compose.yml index 21d72db..e3ade03 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,5 +16,6 @@ services: environment: - API_HOST=0.0.0.0 - API_PORT=8080 - - RUST_BACKTRACE=1 - - DB_CONNECTION_STRING=mongodb://mongo:27017/test \ No newline at end of file + - DB_NAME=docker + - DB_URI=mongodb://mongo:27017 + - RUST_BACKTRACE=1 \ No newline at end of file diff --git a/domain/Cargo.toml b/domain/Cargo.toml index 3e1830c..2fcb6c6 100644 --- a/domain/Cargo.toml +++ b/domain/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" async-trait = { version = "0.1.73", features = [] } serde = "1.0.188" serde_derive = "1.0.188" +bson = "2.7.0" diff --git a/domain/src/queries.rs b/domain/src/queries.rs index 17ad4d2..7b6f035 100644 --- a/domain/src/queries.rs +++ b/domain/src/queries.rs @@ -1,4 +1,7 @@ use async_trait::async_trait; +use bson::serde_helpers::{ + deserialize_hex_string_from_object_id, serialize_hex_string_as_object_id, +}; use serde_derive::{Deserialize, Serialize}; #[async_trait] @@ -12,6 +15,13 @@ pub struct GetUserQuery { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct User { + #[serde( + rename = "_id", + deserialize_with = "deserialize_hex_string_from_object_id", + serialize_with = "serialize_hex_string_as_object_id", + skip_serializing_if = "String::is_empty" + )] + pub id: String, pub name: String, pub age: u8, } diff --git a/domain_impl/src/handlers.rs b/domain_impl/src/handlers.rs index 35223b3..f616cc0 100644 --- a/domain_impl/src/handlers.rs +++ b/domain_impl/src/handlers.rs @@ -12,7 +12,8 @@ pub struct CreateUserCommandHandler { impl ICommandHandler> for CreateUserCommandHandler { async fn handle(&self, cmd: CreateUserCommand) -> Option { let user = User { - name: cmd.name.to_string(), + id: String::new(), + name: cmd.name, age: cmd.age, }; diff --git a/host/src/composition.rs b/host/src/composition.rs new file mode 100644 index 0000000..0c0f03d --- /dev/null +++ b/host/src/composition.rs @@ -0,0 +1,79 @@ +use actix_web::dev::Server; +use actix_web::web::Data; +use actix_web::{App, HttpServer}; +use mongodb::options::{ClientOptions, ServerApi, ServerApiVersion}; +use mongodb::{Client, Collection}; +use std::net::SocketAddr; +use std::sync::Arc; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; + +use crate::conf::AppConf; +use app::endpoints::{create_user, get_user}; +use app::models::*; +use domain::commands::{CreateUserCommand, ICommandHandler}; +use domain::queries::{GetUserQuery, IQueryHandler, User}; +use domain_impl::handlers::{CreateUserCommandHandler, GetUserQueryHandler}; +use infra::repositories::UserRepository; + +#[derive(OpenApi)] +#[openapi( + paths(app::endpoints::create_user, app::endpoints::get_user), + components( + schemas(CreateUserRequest, CreatedUserIdResponse, ErrorResponse), + schemas(UserResponse, ErrorResponse) + ) +)] +struct ApiDoc; +pub struct Composition { + pub server: Server, + pub addrs: Vec, +} + +impl Composition { + pub async fn new(conf: &AppConf) -> Result { + let user_repository_arc = Arc::new(create_user_repository(conf).await); + let command_handler: Arc>> = + Arc::new(CreateUserCommandHandler { + repo: user_repository_arc.clone(), + }); + let query_handler: Arc>> = + Arc::new(GetUserQueryHandler { + repo: user_repository_arc.clone(), + }); + + let openapi = ApiDoc::openapi(); + + let addr = conf.get_api_address(); + let http_server = HttpServer::new(move || { + App::new() + .service( + SwaggerUi::new("/swagger-ui/{_:.*}") + .url("/api-docs/openapi.json", openapi.clone()), + ) + .service(create_user) + .service(get_user) + .app_data(Data::from(command_handler.clone())) + .app_data(Data::from(query_handler.clone())) + }) + .bind(&addr) + .unwrap_or_else(|_| panic!("Failed to bind to the host: {}", &addr)); + + let addrs = http_server.addrs(); + let server = http_server.run(); + + Ok(Self { server, addrs }) + } +} + +async fn create_user_repository(conf: &AppConf) -> UserRepository { + let mut client_options = ClientOptions::parse(&conf.db_uri).await.unwrap(); + + let server_api = ServerApi::builder().version(ServerApiVersion::V1).build(); + client_options.server_api = Some(server_api); + + let client = Client::with_options(client_options).unwrap(); + + let collection: Collection = client.database(&conf.db_name).collection("users"); + UserRepository::new(collection) +} diff --git a/host/src/conf.rs b/host/src/conf.rs index 8866439..8b7ffcb 100644 --- a/host/src/conf.rs +++ b/host/src/conf.rs @@ -1,18 +1,22 @@ pub struct AppConf { pub api_host: String, pub api_port: i32, - pub connection_string: String, + pub db_uri: String, + pub db_name: String, } -pub fn read_app_conf() -> AppConf { - AppConf { - api_host: std::env::var("API_HOST").unwrap_or_else(|_| "0.0.0.0".into()), - api_port: std::env::var("API_PORT") - .unwrap_or_else(|_| "8080".into()) - .parse::() - .expect("wrong api port"), - connection_string: std::env::var("DB_CONNECTION_STRING") - .unwrap_or_else(|_| "mongodb://0.0.0.0:27017/test".into()), +impl AppConf { + pub fn new() -> AppConf { + Self { + db_name: std::env::var("DB_NAME").unwrap_or_else(|_| "test".into()), + api_host: std::env::var("API_HOST").unwrap_or_else(|_| "0.0.0.0".into()), + api_port: std::env::var("API_PORT") + .unwrap_or_else(|_| "8080".into()) + .parse::() + .expect("wrong api port"), + db_uri: std::env::var("DB_URI") + .unwrap_or_else(|_| "mongodb://0.0.0.0:27017/test".into()), + } } } @@ -21,3 +25,9 @@ impl AppConf { format!("{}:{}", self.api_host, self.api_port) } } + +impl Default for AppConf { + fn default() -> Self { + Self::new() + } +} diff --git a/host/src/factory.rs b/host/src/factory.rs deleted file mode 100644 index a8698b0..0000000 --- a/host/src/factory.rs +++ /dev/null @@ -1,77 +0,0 @@ -use actix_web::dev::Server; -use actix_web::web::Data; -use actix_web::{App, HttpServer}; -use mongodb::options::{ClientOptions, ServerApi, ServerApiVersion}; -use mongodb::{Client, Collection}; -use std::net::SocketAddr; -use std::sync::Arc; -use utoipa::OpenApi; -use utoipa_swagger_ui::SwaggerUi; - -use crate::conf::AppConf; -use app::endpoints::{create_user, get_user}; -use app::models::*; -use domain::commands::{CreateUserCommand, ICommandHandler}; -use domain::queries::{GetUserQuery, IQueryHandler, User}; -use domain_impl::handlers::{CreateUserCommandHandler, GetUserQueryHandler}; -use infra::repositories::UserRepository; - -#[derive(OpenApi)] -#[openapi( - paths(app::endpoints::create_user, app::endpoints::get_user), - components( - schemas(CreateUserRequest, CreatedUserIdResponse, ErrorResponse), - schemas(UserResponse, ErrorResponse) - ) -)] -struct ApiDoc; - -pub struct ServerInfo { - pub server: Server, - pub addrs: Vec, -} - -pub async fn create_server(conf: &AppConf) -> Result { - let user_repository_arc = Arc::new(create_user_repository(conf).await); - let command_handler: Arc>> = - Arc::new(CreateUserCommandHandler { - repo: user_repository_arc.clone(), - }); - let query_handler: Arc>> = - Arc::new(GetUserQueryHandler { - repo: user_repository_arc.clone(), - }); - - let openapi = ApiDoc::openapi(); - - let addr = conf.get_api_address(); - let http_server = HttpServer::new(move || { - App::new() - .service( - SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-docs/openapi.json", openapi.clone()), - ) - .service(create_user) - .service(get_user) - .app_data(Data::from(command_handler.clone())) - .app_data(Data::from(query_handler.clone())) - }) - .bind(&addr) - .unwrap_or_else(|_| panic!("Failed to bind to the host: {}", &addr)); - - let addrs = http_server.addrs(); - let server = http_server.run(); - - Ok(ServerInfo { server, addrs }) -} - -async fn create_user_repository(conf: &AppConf) -> UserRepository { - let mut client_options = ClientOptions::parse(&conf.connection_string).await.unwrap(); - - let server_api = ServerApi::builder().version(ServerApiVersion::V1).build(); - client_options.server_api = Some(server_api); - - let client = Client::with_options(client_options).unwrap(); - - let collection: Collection = client.database("asd").collection("users"); - UserRepository::new(collection) -} diff --git a/host/src/lib.rs b/host/src/lib.rs index f9afdda..b333dbd 100644 --- a/host/src/lib.rs +++ b/host/src/lib.rs @@ -1,4 +1,4 @@ +#[path = "composition.rs"] +pub mod composition; #[path = "conf.rs"] pub mod conf; -#[path = "factory.rs"] -pub mod factory; diff --git a/host/src/main.rs b/host/src/main.rs index 1aed637..85160f7 100644 --- a/host/src/main.rs +++ b/host/src/main.rs @@ -1,11 +1,11 @@ -use host::conf::read_app_conf; -use host::factory::create_server; +use host::composition::Composition; +use host::conf::AppConf; #[actix_web::main] async fn main() -> std::io::Result<()> { std::env::set_var("RUST_LOG", "debug"); env_logger::init(); - let conf = read_app_conf(); - create_server(&conf).await.unwrap().server.await + let conf = AppConf::new(); + Composition::new(&conf).await?.server.await } diff --git a/infra/src/repositories.rs b/infra/src/repositories.rs index e3acf88..cd36de0 100644 --- a/infra/src/repositories.rs +++ b/infra/src/repositories.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; -use mongodb::{bson, bson::doc, Collection}; +use mongodb::bson::oid::ObjectId; +use mongodb::{bson::doc, Collection}; use domain::queries::User; use domain_impl::ports::IUserRepository; @@ -18,26 +19,24 @@ impl UserRepository { #[async_trait] impl IUserRepository for UserRepository { async fn create(&self, user: User) -> Option { - let result = self.collection.insert_one(user, None).await; + let user_id = self + .collection + .insert_one(user, None) + .await + .ok()? + .inserted_id + .as_object_id()? + .to_string(); - return match result { - Ok(insertion_result) => Some( - insertion_result - .inserted_id - .as_object_id() - .unwrap() - .to_string(), - ), - Err(_) => None, - }; + Some(user_id) } async fn get(&self, id: String) -> Option { - let user_id = bson::oid::ObjectId::parse_str(&id).ok()?; - return self - .collection + let user_id = ObjectId::parse_str(&id).ok()?; + + self.collection .find_one(doc! { "_id": user_id }, None) .await - .ok()?; + .ok()? } } diff --git a/integrtion-tests/tests/sut.rs b/integrtion-tests/tests/sut.rs index 23bdd01..1585cf8 100644 --- a/integrtion-tests/tests/sut.rs +++ b/integrtion-tests/tests/sut.rs @@ -1,7 +1,7 @@ use actix_web::rt; use app::models::{CreateUserRequest, CreatedUserIdResponse, UserResponse}; +use host::composition::Composition; use host::conf::AppConf; -use host::factory::create_server; use reqwest::StatusCode; #[derive(Clone)] @@ -14,10 +14,11 @@ impl Sut { let conf = AppConf { api_host: "127.0.0.1".to_string(), api_port: 0, - connection_string: "mongodb://127.0.0.1/test".to_string(), + db_uri: "mongodb://127.0.0.1/test".to_string(), + db_name: "test".to_string(), }; - let info = create_server(&conf).await.unwrap(); + let info = Composition::new(&conf).await.unwrap(); let base_url = format!("http://{}:{}", info.addrs[0].ip(), info.addrs[0].port()); rt::spawn(info.server);