#microservices #amqp #storage #requests #layer #http #rest

bin+lib cp-microservice

Cuplan的微服务模板

52次发布

5123.0.0 2023年9月6日
0.1.110 2023年10月26日
0.1.89 2023年9月30日
0.1.20 2023年8月22日
0.1.8 2023年7月25日

#17 in #microservices

Download history 1/week @ 2024-03-08 1/week @ 2024-03-15 246/week @ 2024-03-29 56/week @ 2024-04-05

442 每月下载量

MIT 许可证

84KB
2K SLoC

CI/CD

简介

cp-microservice 旨在成为一个工具库,以便您能够轻松地使用 Rust 创建微服务。目前所有努力都集中在基于 AMQP 的 API 上,尽管该库可以很容易地适配通过 HTTP 暴露 REST API。

架构

cp-microservice 为 Rust 微服务提出的架构是基于三个并行运行的层的概念。这些层如下

  1. API:在此处,通过向 Logic 层发送请求,对传入的请求进行路由和处理。
  2. Logic:业务逻辑位于此处。在此处处理传入的逻辑请求,并且每当需要与存储相关的操作时,将请求发送到存储层。
  3. Storage:在此处通过直接调用数据库或正在使用的任何存储系统来处理存储请求。

入门

为了开始使用这个库,您可以参考以下项目 cp-organization。它包含此库的预期用法。但是,以下是成功实现您的微服务项目的步骤

  1. 通过在您的 Cargo.toml 中添加以下行,将 cp-microservice 添加为项目的依赖项: cp-microservice = "0.1"

  2. 接下来,我们在 api 模块(src/api)中创建一个新的文件 api_actions.rs。该文件的内容是一个公共函数 get_api_actions,它将返回通过 API 可用的所有操作。以下是从 cp-organizationapi_actions.rs 文件的示例

     use std::{collections::HashMap, sync::Arc};
    
     use cp_microservice::api::server::input::action::Action;
     
     use crate::logic::logic_request::LogicRequest;
     
     pub fn get_api_actions() -> HashMap<String, Action<LogicRequest>> {
         let mut actions: HashMap<String, Action<LogicRequest>> = HashMap::new();
     
         actions.insert(
             "create_org".to_string(),
             Action::new(
                 "create_org".to_string(),
                 Arc::new(move |request, sender| {
                     Box::pin(crate::api::actions::create_org::create_org(request, sender))
                 }),
                 Vec::new(),
             ),
         );
     
         actions.insert(
             "create_invitation_code".to_string(),
             Action::new(
                 "create_invitation_code".to_string(),
                 Arc::new(move |request, sender| {
                     Box::pin(
                         crate::api::actions::create_invitation_code::create_invitation_code(
                             request, sender,
                         ),
                     )
                 }),
                 Vec::new(),
             ),
         );
     
         actions
     }
    
  3. 接下来,我们可以定义自定义插件来定义关于处理通过公开的 API 传入的请求的定制行为。自定义插件必须列在 api_plugins.rs 文件中,该文件必须包含在 api 模块(src/api)中。以下是从 cp-organization 的示例

     use std::sync::Arc;
    
     use cp_microservice::{api::server::input::input_plugin::InputPlugin, core::error::Error};
     
     pub async fn get_api_plugins() -> Result<Vec<Arc<dyn InputPlugin + Send + Sync>>, Error> {
         let api_plugins: Vec<Arc<dyn InputPlugin + Send + Sync>> = vec![];
     
         Ok(api_plugins)
     }
    
  4. 现在我们已经定义了API的操作和插件。我们可以继续在微服务的 `main.rs` 文件中初始化我们的微服务。以下是使用 cp-microservice 初始化微服务的最小代码。

     let secrets_manager: Arc<dyn SecretsManager> = get_secrets_manager()?;
     let amqp_connection_config = get_amqp_connection_config(&secrets_manager)?;
     let amqp_api = get_amqp_api()?;
    
     let api_actions: HashMap<String, Action<LogicRequest>> = get_api_actions();
    
     let api_plugins: Vec<Arc<dyn InputPlugin + Send + Sync>> = match get_api_plugins().await {
         Ok(api_plugins) => api_plugins,
         Err(error) => {
             return Err(Error::new(
                 ErrorKind::InvalidData,
                 format!("failed to get API plugins: {}", &error),
             ))
         }
     };
    
     let api_initialization_package = ApiInitializationPackage::<LogicRequest> {
         amqp_connection_config,
         amqp_api,
         actions: api_actions,
         plugins: api_plugins,
     };
    
     let logic_executors = get_logic_executors();
    
     let (storage_request_sender, storage_request_receiver) =
         async_channel::bounded::<StorageRequest>(1024usize);
    
     let logic_initialization_package = LogicInitializationPackage::<LogicRequest, StorageRequest> {
         executors: logic_executors,
         storage_request_sender,
     };
    
     match try_initialize_microservice(api_initialization_package, logic_initialization_package)
         .await
     {
         Ok(_) => (),
         Err(error) => return Err(error),
     };
    

    在前面的代码中调用的初始化函数可以存储在一个例如 init.rs 文件中,就像在 `cp-organization` 中一样。

     use std::{
         io::{Error, ErrorKind},
         sync::Arc,
     };
     
     use cp_microservice::{
         core::secrets::secrets_manager::SecretsManager,
         r#impl::{
             api::shared::amqp_api_entry::AmqpApiEntry,
             core::bitwarden_secrets_manager::BitwardenSecretsManager,
         },
     };
     use mongodb::{options::ClientOptions, Client};
     use multiple_connections_lapin_wrapper::config::amqp_connect_config::AmqpConnectConfig;
     
     const SECRETS_MANAGER_ACCESS_TOKEN_ENV: &str = "CP_ORGANIZATION_SECRETS_MANAGER_ACCESS_TOKEN";
     const AMQP_CONNECTION_CONFIG_SECRET_ENV: &str = "CP_ORGANIZATION_AMQP_CONNECTION_SECRET";
     const MONGODB_CONNECTION_CONFIG_SECRET_ENV: &str = "CP_ORGANIZATION_MONGODB_CONNECTION_SECRET";
     
     pub fn get_secrets_manager() -> Result<Arc<dyn SecretsManager>, Error> {
         let access_token = match std::env::var(SECRETS_MANAGER_ACCESS_TOKEN_ENV) {
             Ok(access_token) => access_token,
             Err(_) => {
                 return Err(Error::new(
                     ErrorKind::InvalidInput,
                     "no access token provided",
                 ));
             }
         };
     
         Ok(Arc::new(BitwardenSecretsManager::new(access_token)))
     }
     
     pub fn get_amqp_connection_config(
         secrets_manager: &Arc<dyn SecretsManager>,
     ) -> Result<AmqpConnectConfig, Error> {
         let secret_id = match std::env::var(AMQP_CONNECTION_CONFIG_SECRET_ENV) {
             Ok(secret_id) => secret_id,
             Err(_) => {
                 return Err(Error::new(
                     ErrorKind::InvalidInput,
                     format!(
                         "failed to read secret id '{}'",
                         AMQP_CONNECTION_CONFIG_SECRET_ENV
                     ),
                 ));
             }
         };
     
         let amqp_connection_config_json = match secrets_manager.get(&secret_id) {
             Some(amqp_connection_config_json) => amqp_connection_config_json,
             None => {
                 return Err(Error::new(
                     ErrorKind::InvalidInput,
                     format!("no secret with id '{}'", &secret_id),
                 ));
             }
         };
     
         let amqp_connection_config =
             match serde_json::from_str::<AmqpConnectConfig>(&amqp_connection_config_json) {
                 Ok(amqp_connection_config) => amqp_connection_config,
                 Err(error) => {
                     return Err(Error::new(
                         ErrorKind::InvalidData,
                         format!("secret contains invalid amqp connection config: {}", &error),
                     ));
                 }
             };
     
         Ok(amqp_connection_config)
     }
     
     pub fn get_amqp_api() -> Result<Vec<AmqpApiEntry>, Error> {
         let amqp_api_file = match std::env::args().nth(1) {
             Some(amqp_api_file) => amqp_api_file,
             None => {
                 return Err(Error::new(
                     ErrorKind::InvalidInput,
                     "no amqp api file provided",
                 ));
             }
         };
     
         let amqp_api_file_content = match std::fs::read_to_string(&amqp_api_file) {
             Ok(content) => content,
             Err(error) => {
                 return Err(Error::new(
                     ErrorKind::NotFound,
                     format!(
                         "failed to find amqp api file '{}': {}",
                         &amqp_api_file, &error
                     ),
                 ))
             }
         };
     
         let amqp_api = match serde_json::from_str::<Vec<AmqpApiEntry>>(&amqp_api_file_content) {
             Ok(amqp_api) => amqp_api,
             Err(error) => {
                 return Err(Error::new(
                     ErrorKind::InvalidData,
                     format!("failed to deserialize AMQP API file: {}", &error),
                 ))
             }
         };
     
         Ok(amqp_api)
     }
     
     pub fn get_mongodb_client(secrets_manager: &Arc<dyn SecretsManager>) -> Result<Client, Error> {
         let secret_id = match std::env::var(MONGODB_CONNECTION_CONFIG_SECRET_ENV) {
             Ok(secret_id) => secret_id,
             Err(_) => {
                 return Err(Error::new(
                     ErrorKind::InvalidInput,
                     format!(
                         "failed to read secret id '{}'",
                         MONGODB_CONNECTION_CONFIG_SECRET_ENV
                     ),
                 ));
             }
         };
     
         let mongodb_connection_config_json = match secrets_manager.get(&secret_id) {
             Some(mongodb_connection_config_json) => mongodb_connection_config_json,
             None => {
                 return Err(Error::new(
                     ErrorKind::InvalidInput,
                     format!("no secret with id '{}'", &secret_id),
                 ));
             }
         };
     
         let mongodb_client_options =
             match serde_json::from_str::<ClientOptions>(&mongodb_connection_config_json) {
                 Ok(mongodb_client_options) => mongodb_client_options,
                 Err(error) => {
                     return Err(Error::new(
                         ErrorKind::InvalidData,
                         format!(
                             "failed to deserialize MongoDB connection config: {}",
                             &error
                         ),
                     ))
                 }
             };
     
         let mongodb_client = match Client::with_options(mongodb_client_options) {
             Ok(mongodb_client) => mongodb_client,
             Err(error) => {
                 return Err(Error::new(
                     ErrorKind::InvalidData,
                     format!("failed to create MongoDB client: {}", &error),
                 ))
             }
         };
     
         Ok(mongodb_client)
     }
    
    

这就是使用 cp-microservice 配置基本微服务的全部内容。

目标

cp-microservice 的当前目标是使通常使用 Spring Boot 或类似技术创建的微服务的创建变得容易管理,使用 Rust 实现。

依赖项

~12–27MB
~434K SLoC