feat: ApplicationModule with tentative architecture and placeholder implementation of first few features

This commit is contained in:
Jean-Gabriel Gill-Couture 2025-06-28 22:50:06 -04:00
parent 22847fc42a
commit 923de4506e
4 changed files with 177 additions and 1 deletions

View File

@ -0,0 +1,75 @@
use async_trait::async_trait;
use crate::modules::application::{Application, ApplicationFeature};
#[derive(Debug)]
pub struct PublicEndpoint {
application_port: u16,
}
/// Use port 3000 as default port. Harmony wants to provide "sane defaults" in general, and in this
/// particular context, using port 80 goes against our philosophy to provide production grade
/// defaults out of the box. Using an unprivileged port is a good security practice and will allow
/// for unprivileged containers to work with this out of the box.
///
/// Now, why 3000 specifically? Many popular web/network frameworks use it by default, there is no
/// perfect answer for this but many Rust and Python libraries tend to use 3000.
impl Default for PublicEndpoint {
fn default() -> Self {
Self {
application_port: 3000,
}
}
}
#[async_trait]
impl ApplicationFeature for PublicEndpoint {
async fn ensure_installed(&self) -> Result<(), String> {
todo!()
}
async fn is_installed(&self) -> Result<bool, String> {
todo!()
}
async fn uninstall(&self) -> Result<(), String> {
todo!()
}
}
// Design options here :
//
// 1. Forget about ApplicationFeature trait. The Features are just other scores that are passed on to
// the ApplicationInterpret as children (and we can rename children dependencies maybe?)
//
// 2. Go forward with the ApplicationFeature trait. There are important question marks here :
// - What about the installation lifecycle management? This was defined as being handled by a
// Topology. The thing here is that I am not sure wether application features belong at the
// Topology level or not. Functionnaly they are pretty similar. Topology provides software
// infrastructure features that Scores will then install themselves on. Most of the time those very
// features are installed using Scores with lower level dependencies. For example, :
//
// AlertingFeature depends on T: Topology + AlertSender
// AlertSender is implemented as KubePrometheus which depends on T: Topology + HelmCommand
// HelmCommand relies on T: Topology + K8sClient
//
// With that said, would it work with `features: Vec<box dyn Score<T>>` instead of `features:
// Vec<box dyn ApplicationFeature>>` ?
//
// Let's unpack this :
//
// RustWebappScore<T: Topology> {
// features: Vec<box dyn Score<T>>,
// }
//
// This brings in a significant problem : RustWebappScore becomes generic, which is a problem for
// Clone and Serialize bounds.
//
// But that can be fixed easily I think ?
//
// RustWebappScore<T: Topology + Clone + Serialize> {
// features: Vec<box dyn Score<T>>,
// }
//
// Oh right not quite because it is `dyn`.
//

View File

@ -0,0 +1,54 @@
mod endpoint;
use async_trait::async_trait;
pub use endpoint::*;
use super::ApplicationFeature;
#[derive(Debug, Default)]
pub struct SoftwareQualityChecks {}
#[async_trait]
impl ApplicationFeature for SoftwareQualityChecks {
async fn ensure_installed(&self) -> Result<(), String> {
todo!()
}
async fn is_installed(&self) -> Result<bool, String> {
todo!()
}
async fn uninstall(&self) -> Result<(), String> {
todo!()
}
}
#[derive(Debug, Default)]
pub struct ContinuousDelivery {}
#[async_trait]
impl ApplicationFeature for ContinuousDelivery {
async fn ensure_installed(&self) -> Result<(), String> {
todo!()
}
async fn is_installed(&self) -> Result<bool, String> {
todo!()
}
async fn uninstall(&self) -> Result<(), String> {
todo!()
}
}
#[derive(Debug, Default)]
pub struct Monitoring {}
#[async_trait]
impl ApplicationFeature for Monitoring {
async fn ensure_installed(&self) -> Result<(), String> {
todo!()
}
async fn is_installed(&self) -> Result<bool, String> {
todo!()
}
async fn uninstall(&self) -> Result<(), String> {
todo!()
}
}

View File

@ -1,3 +1,7 @@
mod rust;
pub mod features;
pub use rust::*;
use async_trait::async_trait;
use serde::Serialize;
@ -29,7 +33,7 @@ impl<T: Topology> Score<T> for GoApplicationScore {
#[derive(Debug)]
pub struct ApplicationInterpret {
pub features: Vec<Box<dyn ApplicationFeature>>,
features: Vec<Box<dyn ApplicationFeature>>,
}
#[async_trait]
@ -55,6 +59,8 @@ impl<T: Topology> Interpret<T> for ApplicationInterpret {
}
}
trait Application {}
/// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability,
/// ContinuousIntegration, ContinuousDelivery
#[async_trait]
@ -64,6 +70,21 @@ pub trait ApplicationFeature: std::fmt::Debug + Send + Sync {
async fn uninstall(&self) -> Result<(), String>;
}
impl Serialize for Box<dyn ApplicationFeature> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
todo!()
}
}
impl Clone for Box<dyn ApplicationFeature> {
fn clone(&self) -> Self {
todo!()
}
}
#[derive(Debug)]
pub struct BackupFeature;

View File

@ -0,0 +1,26 @@
use serde::Serialize;
use crate::{
score::Score,
topology::{Topology, Url},
};
use super::{ApplicationFeature, ApplicationInterpret};
#[derive(Debug, Serialize, Clone)]
pub struct RustWebappScore {
pub name: String,
pub domain: Url,
pub features: Vec<Box<dyn ApplicationFeature>>,
}
impl<T: Topology> Score<T> for RustWebappScore {
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
Box::new(ApplicationInterpret { features: todo!() })
}
fn name(&self) -> String {
format!("{}-RustWebapp", self.name)
}
}