refactor: migrate to DDD layered architecture
Split the monolithic app.rs (762 lines) into four DDD layers: - domain/: models, navigation enums (Tab, ModalKind), conversions, aggregates - application/: App state, LibraryState, NotificationManager, event handlers - infrastructure/: config, gRPC client, system utilities - presentation/: all render functions, widgets, views, modals Original modules (app, data, ui, config, grpc) preserved as thin re-export facades for backward compatibility. All 13 insta snapshot tests pass without modification.
This commit is contained in:
+1
-97
@@ -1,97 +1 @@
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::transport::Channel;
|
||||
|
||||
use crate::proto::music_agregator_v1::music_agregator_service_client::MusicAgregatorServiceClient;
|
||||
|
||||
pub use crate::proto::music_agregator_v1::{
|
||||
AlbumDetail, ArtistSummary, GetAlbumRequest, GetArtistsRequest, TrackDetail,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GrpcRequest {
|
||||
GetArtists,
|
||||
GetAlbum { album_id: String },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code, clippy::large_enum_variant)]
|
||||
pub enum GrpcResponse {
|
||||
Artists(Vec<ArtistSummary>),
|
||||
Album {
|
||||
album: AlbumDetail,
|
||||
tracks: Vec<TrackDetail>,
|
||||
},
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub struct GrpcClient {
|
||||
music: MusicAgregatorServiceClient<Channel>,
|
||||
}
|
||||
|
||||
impl GrpcClient {
|
||||
pub async fn connect(addr: &str) -> Result<Self, tonic::transport::Error> {
|
||||
let channel = Channel::from_shared(addr.to_string())
|
||||
.expect("valid uri")
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
Ok(Self {
|
||||
music: MusicAgregatorServiceClient::new(channel),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_artists(&mut self) -> Result<Vec<ArtistSummary>, tonic::Status> {
|
||||
let response = self.music.get_artists(GetArtistsRequest {}).await?;
|
||||
Ok(response.into_inner().artists)
|
||||
}
|
||||
|
||||
pub async fn get_album(
|
||||
&mut self,
|
||||
album_id: String,
|
||||
) -> Result<(AlbumDetail, Vec<TrackDetail>), tonic::Status> {
|
||||
let response = self.music.get_album(GetAlbumRequest { album_id }).await?;
|
||||
let inner = response.into_inner();
|
||||
let album = inner
|
||||
.album
|
||||
.ok_or_else(|| tonic::Status::not_found("Album not found in response"))?;
|
||||
Ok((album, inner.tracks))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_grpc_worker(
|
||||
addr: String,
|
||||
) -> (mpsc::Sender<GrpcRequest>, mpsc::Receiver<GrpcResponse>) {
|
||||
let (req_tx, mut req_rx) = mpsc::channel::<GrpcRequest>(32);
|
||||
let (resp_tx, resp_rx) = mpsc::channel::<GrpcResponse>(32);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let client = match GrpcClient::connect(&addr).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let _ = resp_tx.send(GrpcResponse::Error(e.to_string())).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut client = client;
|
||||
|
||||
while let Some(request) = req_rx.recv().await {
|
||||
let response = match request {
|
||||
GrpcRequest::GetArtists => match client.get_artists().await {
|
||||
Ok(artists) => GrpcResponse::Artists(artists),
|
||||
Err(e) => GrpcResponse::Error(e.to_string()),
|
||||
},
|
||||
GrpcRequest::GetAlbum { album_id } => match client.get_album(album_id).await {
|
||||
Ok((album, tracks)) => GrpcResponse::Album { album, tracks },
|
||||
Err(e) => GrpcResponse::Error(e.to_string()),
|
||||
},
|
||||
};
|
||||
|
||||
if resp_tx.send(response).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(req_tx, resp_rx)
|
||||
}
|
||||
pub use crate::infrastructure::grpc::*;
|
||||
|
||||
Reference in New Issue
Block a user