Split orchestration by durable use case
This commit is contained in:
parent
069f3813ae
commit
fa81cb7f21
2 changed files with 609 additions and 513 deletions
|
|
@ -21,6 +21,8 @@ use crate::{
|
|||
TargetRegistry,
|
||||
};
|
||||
|
||||
mod lifecycle;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DeletionService {
|
||||
pool: PgPool,
|
||||
|
|
@ -46,156 +48,6 @@ impl DeletionService {
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn soft_delete(
|
||||
&self,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let outcome = Self::soft_delete_in_tx(&mut tx, principal, job_id).await?;
|
||||
tx.commit().await?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn soft_delete_in_tx(
|
||||
tx: &mut PgTx<'_>,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let row = lock_job(tx, job_id).await?;
|
||||
authorize(principal, &row, ManagementOperation::SoftDelete)?;
|
||||
require_terminal(&row.phase)?;
|
||||
if row.deletion_operation_id.is_some() {
|
||||
return Err(DeletionError::HardDeleteStarted);
|
||||
}
|
||||
if row.lifecycle == "soft_deleted" {
|
||||
return Ok(LifecycleMutation::Unchanged);
|
||||
}
|
||||
let now = OffsetDateTime::now_utc();
|
||||
sqlx::query(
|
||||
"UPDATE jobs
|
||||
SET lifecycle = 'soft_deleted', revision = revision + 1, updated_at = $2
|
||||
WHERE job_id = $1",
|
||||
)
|
||||
.bind(job_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
enqueue(tx, "job.updated", json!({ "job_id": job_id })).await?;
|
||||
Ok(LifecycleMutation::Changed)
|
||||
}
|
||||
|
||||
pub async fn restore(
|
||||
&self,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let outcome = Self::restore_in_tx(&mut tx, principal, job_id).await?;
|
||||
tx.commit().await?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn restore_in_tx(
|
||||
tx: &mut PgTx<'_>,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let row = lock_job(tx, job_id).await?;
|
||||
authorize(principal, &row, ManagementOperation::Restore)?;
|
||||
require_terminal(&row.phase)?;
|
||||
if row.deletion_operation_id.is_some() {
|
||||
return Err(DeletionError::HardDeleteStarted);
|
||||
}
|
||||
if row.lifecycle == "visible" {
|
||||
return Ok(LifecycleMutation::Unchanged);
|
||||
}
|
||||
let now = OffsetDateTime::now_utc();
|
||||
sqlx::query(
|
||||
"UPDATE jobs
|
||||
SET lifecycle = 'visible', revision = revision + 1, updated_at = $2
|
||||
WHERE job_id = $1",
|
||||
)
|
||||
.bind(job_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
enqueue(tx, "job.updated", json!({ "job_id": job_id })).await?;
|
||||
Ok(LifecycleMutation::Changed)
|
||||
}
|
||||
|
||||
pub async fn hard_delete(
|
||||
&self,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<HardDeleteOutcome, DeletionError> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let outcome = Self::hard_delete_in_tx(&mut tx, principal, job_id).await?;
|
||||
tx.commit().await?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn hard_delete_in_tx(
|
||||
tx: &mut PgTx<'_>,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<HardDeleteOutcome, DeletionError> {
|
||||
let operation_id = new_id('d')?;
|
||||
let row = lock_job(tx, job_id).await?;
|
||||
authorize(principal, &row, ManagementOperation::HardDelete)?;
|
||||
require_terminal(&row.phase)?;
|
||||
if let Some(existing) = row.deletion_operation_id {
|
||||
let operation = load_operation_in_tx(tx, &existing).await?;
|
||||
return Ok(HardDeleteOutcome {
|
||||
mutation: LifecycleMutation::Unchanged,
|
||||
operation,
|
||||
});
|
||||
}
|
||||
validate_committed_inventory(tx, &row).await?;
|
||||
validate_deletion_identities(tx, &row).await?;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
sqlx::query(
|
||||
"INSERT INTO deletion_operations (
|
||||
operation_id, job_id, state, created_at, updated_at
|
||||
) VALUES ($1, $2, 'queued', $3, $3)",
|
||||
)
|
||||
.bind(operation_id.as_str())
|
||||
.bind(job_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"UPDATE jobs
|
||||
SET lifecycle = 'hard_deleting', deletion_operation_id = $2,
|
||||
hard_delete_started_at = $3, revision = revision + 1, updated_at = $3
|
||||
WHERE job_id = $1",
|
||||
)
|
||||
.bind(job_id.as_str())
|
||||
.bind(operation_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
materialize_items(tx, job_id, &operation_id, now).await?;
|
||||
finalize_if_empty(tx, job_id, &operation_id, now).await?;
|
||||
enqueue(
|
||||
tx,
|
||||
"job.updated",
|
||||
json!({ "job_id": job_id, "deletion_operation_id": operation_id }),
|
||||
)
|
||||
.await?;
|
||||
enqueue(
|
||||
tx,
|
||||
"deletion.updated",
|
||||
json!({ "job_id": job_id, "operation_id": operation_id }),
|
||||
)
|
||||
.await?;
|
||||
let operation = load_operation_in_tx(tx, operation_id.as_str()).await?;
|
||||
Ok(HardDeleteOutcome {
|
||||
mutation: LifecycleMutation::Changed,
|
||||
operation,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn operation(
|
||||
&self,
|
||||
principal: &Principal,
|
||||
|
|
|
|||
153
crates/remora-deletion/src/service/lifecycle.rs
Normal file
153
crates/remora-deletion/src/service/lifecycle.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
use super::*;
|
||||
|
||||
impl DeletionService {
|
||||
pub async fn soft_delete(
|
||||
&self,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let outcome = Self::soft_delete_in_tx(&mut tx, principal, job_id).await?;
|
||||
tx.commit().await?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn soft_delete_in_tx(
|
||||
tx: &mut PgTx<'_>,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let row = lock_job(tx, job_id).await?;
|
||||
authorize(principal, &row, ManagementOperation::SoftDelete)?;
|
||||
require_terminal(&row.phase)?;
|
||||
if row.deletion_operation_id.is_some() {
|
||||
return Err(DeletionError::HardDeleteStarted);
|
||||
}
|
||||
if row.lifecycle == "soft_deleted" {
|
||||
return Ok(LifecycleMutation::Unchanged);
|
||||
}
|
||||
let now = OffsetDateTime::now_utc();
|
||||
sqlx::query(
|
||||
"UPDATE jobs
|
||||
SET lifecycle = 'soft_deleted', revision = revision + 1, updated_at = $2
|
||||
WHERE job_id = $1",
|
||||
)
|
||||
.bind(job_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
enqueue(tx, "job.updated", json!({ "job_id": job_id })).await?;
|
||||
Ok(LifecycleMutation::Changed)
|
||||
}
|
||||
|
||||
pub async fn restore(
|
||||
&self,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let outcome = Self::restore_in_tx(&mut tx, principal, job_id).await?;
|
||||
tx.commit().await?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn restore_in_tx(
|
||||
tx: &mut PgTx<'_>,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<LifecycleMutation, DeletionError> {
|
||||
let row = lock_job(tx, job_id).await?;
|
||||
authorize(principal, &row, ManagementOperation::Restore)?;
|
||||
require_terminal(&row.phase)?;
|
||||
if row.deletion_operation_id.is_some() {
|
||||
return Err(DeletionError::HardDeleteStarted);
|
||||
}
|
||||
if row.lifecycle == "visible" {
|
||||
return Ok(LifecycleMutation::Unchanged);
|
||||
}
|
||||
let now = OffsetDateTime::now_utc();
|
||||
sqlx::query(
|
||||
"UPDATE jobs
|
||||
SET lifecycle = 'visible', revision = revision + 1, updated_at = $2
|
||||
WHERE job_id = $1",
|
||||
)
|
||||
.bind(job_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
enqueue(tx, "job.updated", json!({ "job_id": job_id })).await?;
|
||||
Ok(LifecycleMutation::Changed)
|
||||
}
|
||||
|
||||
pub async fn hard_delete(
|
||||
&self,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<HardDeleteOutcome, DeletionError> {
|
||||
let mut tx = self.pool.begin().await?;
|
||||
let outcome = Self::hard_delete_in_tx(&mut tx, principal, job_id).await?;
|
||||
tx.commit().await?;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn hard_delete_in_tx(
|
||||
tx: &mut PgTx<'_>,
|
||||
principal: &Principal,
|
||||
job_id: &OpaqueId,
|
||||
) -> Result<HardDeleteOutcome, DeletionError> {
|
||||
let operation_id = new_id('d')?;
|
||||
let row = lock_job(tx, job_id).await?;
|
||||
authorize(principal, &row, ManagementOperation::HardDelete)?;
|
||||
require_terminal(&row.phase)?;
|
||||
if let Some(existing) = row.deletion_operation_id {
|
||||
let operation = load_operation_in_tx(tx, &existing).await?;
|
||||
return Ok(HardDeleteOutcome {
|
||||
mutation: LifecycleMutation::Unchanged,
|
||||
operation,
|
||||
});
|
||||
}
|
||||
validate_committed_inventory(tx, &row).await?;
|
||||
validate_deletion_identities(tx, &row).await?;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
sqlx::query(
|
||||
"INSERT INTO deletion_operations (
|
||||
operation_id, job_id, state, created_at, updated_at
|
||||
) VALUES ($1, $2, 'queued', $3, $3)",
|
||||
)
|
||||
.bind(operation_id.as_str())
|
||||
.bind(job_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"UPDATE jobs
|
||||
SET lifecycle = 'hard_deleting', deletion_operation_id = $2,
|
||||
hard_delete_started_at = $3, revision = revision + 1, updated_at = $3
|
||||
WHERE job_id = $1",
|
||||
)
|
||||
.bind(job_id.as_str())
|
||||
.bind(operation_id.as_str())
|
||||
.bind(now)
|
||||
.execute(tx.as_mut())
|
||||
.await?;
|
||||
materialize_items(tx, job_id, &operation_id, now).await?;
|
||||
finalize_if_empty(tx, job_id, &operation_id, now).await?;
|
||||
enqueue(
|
||||
tx,
|
||||
"job.updated",
|
||||
json!({ "job_id": job_id, "deletion_operation_id": operation_id }),
|
||||
)
|
||||
.await?;
|
||||
enqueue(
|
||||
tx,
|
||||
"deletion.updated",
|
||||
json!({ "job_id": job_id, "operation_id": operation_id }),
|
||||
)
|
||||
.await?;
|
||||
let operation = load_operation_in_tx(tx, operation_id.as_str()).await?;
|
||||
Ok(HardDeleteOutcome {
|
||||
mutation: LifecycleMutation::Changed,
|
||||
operation,
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue