Phase out older message versions without breaking clients:
- Accept versions N and N-1 on ingress; rewrite legacy payloads in middleware so downstream handlers see the current schema.[^10][^12]
- Emit version N on egress so clients observe a single schema.
- Publish metrics and logs describing legacy usage to support operator dashboards.[^33][^8]
- Remove adapters once the sunset window ends.
use std::sync::Arc;
use wireframe::{
app::{Envelope, Handler, WireframeApp},
middleware,
message::Message,
Result,
};
#[derive(bincode::Encode, bincode::BorrowDecode)]
struct MsgV1 {
id: u64,
name: String,
}
#[derive(bincode::Encode, bincode::BorrowDecode)]
struct MsgV2 {
id: u64,
display_name: String,
}
impl From<MsgV1> for MsgV2 {
fn from(v1: MsgV1) -> Self {
Self {
id: v1.id,
display_name: v1.name,
}
}
}
async fn handle_v2(_env: &Envelope) {}
fn build_app() -> Result<WireframeApp> {
let handler: Handler<Envelope> = Arc::new(|env: &Envelope| Box::pin(handle_v2(env)));
WireframeApp::new()?
.route(42, handler)?
.wrap(middleware::from_fn(|mut req, next| async move {
if let Ok((legacy, _)) = MsgV1::from_bytes(req.frame()) {
let upgraded = MsgV2::from(legacy);
let bytes = upgraded.to_bytes().expect("encode MsgV2");
req.frame_mut().clear();
req.frame_mut().extend_from_slice(&bytes);
}
let mut response = next.call(req).await?;
if let Ok((legacy, _)) = MsgV1::from_bytes(response.frame()) {
let upgraded = MsgV2::from(legacy);
let bytes = upgraded.to_bytes().expect("encode MsgV2");
response.frame_mut().clear();
response.frame_mut().extend_from_slice(&bytes);
}
Ok(response)
}))
}