refactor(ports): CQRS split — FederationActionPort into four focused sub-ports

This commit is contained in:
2026-05-15 13:49:58 +02:00
parent 8ed7f3d5bc
commit 189901b778
6 changed files with 155 additions and 95 deletions

View File

@@ -1626,7 +1626,7 @@ impl domain::ports::FederationSchedulerPort for ActivityPubService {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl domain::ports::FederationActionPort for ActivityPubService { impl domain::ports::FederationLookupPort for ActivityPubService {
async fn lookup_actor( async fn lookup_actor(
&self, &self,
handle: &str, handle: &str,
@@ -1703,31 +1703,6 @@ impl domain::ports::FederationActionPort for ActivityPubService {
}) })
} }
async fn follow_remote(
&self,
local_user_id: &domain::value_objects::UserId,
handle: &str,
) -> Result<(), domain::errors::DomainError> {
self.follow(local_user_id.as_uuid(), handle)
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
}
async fn unfollow_remote(
&self,
local_user_id: &domain::value_objects::UserId,
handle: &str,
) -> Result<(), domain::errors::DomainError> {
let data = self.federation_config.to_request_data();
let remote_actor: DbActor = Self::webfinger_https(handle, &data)
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?;
let actor_url = remote_actor.ap_id.to_string();
self.unfollow(local_user_id.as_uuid(), &actor_url)
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
}
async fn actor_json( async fn actor_json(
&self, &self,
user_id: &domain::value_objects::UserId, user_id: &domain::value_objects::UserId,
@@ -1832,7 +1807,10 @@ impl domain::ports::FederationActionPort for ActivityPubService {
serde_json::to_string(&obj) serde_json::to_string(&obj)
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
} }
}
#[async_trait::async_trait]
impl domain::ports::FederationFetchPort for ActivityPubService {
async fn fetch_outbox_page( async fn fetch_outbox_page(
&self, &self,
outbox_url: &str, outbox_url: &str,
@@ -2026,7 +2004,48 @@ impl domain::ports::FederationActionPort for ActivityPubService {
}) })
.collect() .collect()
} }
}
#[async_trait::async_trait]
impl domain::ports::FederationFollowPort for ActivityPubService {
async fn follow_remote(
&self,
local_user_id: &domain::value_objects::UserId,
handle: &str,
) -> Result<(), domain::errors::DomainError> {
self.follow(local_user_id.as_uuid(), handle)
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
}
async fn unfollow_remote(
&self,
local_user_id: &domain::value_objects::UserId,
handle: &str,
) -> Result<(), domain::errors::DomainError> {
let data = self.federation_config.to_request_data();
let remote_actor: DbActor = Self::webfinger_https(handle, &data)
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?;
let actor_url = remote_actor.ap_id.to_string();
self.unfollow(local_user_id.as_uuid(), &actor_url)
.await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
}
async fn get_remote_following(
&self,
user_id: &domain::value_objects::UserId,
) -> Result<Vec<domain::models::remote_actor::RemoteActor>, domain::errors::DomainError> {
self.get_following(user_id.as_uuid())
.await
.map(|v| v.into_iter().map(Self::adapter_actor_to_domain).collect())
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
}
}
#[async_trait::async_trait]
impl domain::ports::FederationFollowRequestPort for ActivityPubService {
async fn get_pending_followers( async fn get_pending_followers(
&self, &self,
user_id: &domain::value_objects::UserId, user_id: &domain::value_objects::UserId,
@@ -2076,16 +2095,6 @@ impl domain::ports::FederationActionPort for ActivityPubService {
.await .await
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
} }
async fn get_remote_following(
&self,
user_id: &domain::value_objects::UserId,
) -> Result<Vec<domain::models::remote_actor::RemoteActor>, domain::errors::DomainError> {
self.get_following(user_id.as_uuid())
.await
.map(|v| v.into_iter().map(Self::adapter_actor_to_domain).collect())
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
}
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -1,10 +1,28 @@
fn _assert_impl_federation_action_port() fn _assert_impl_federation_lookup_port()
where where
crate::service::ActivityPubService: domain::ports::FederationActionPort, crate::service::ActivityPubService: domain::ports::FederationLookupPort,
{ {
} }
fn _assert_impl_federation_action_port_connections() fn _assert_impl_federation_follow_port()
where
crate::service::ActivityPubService: domain::ports::FederationFollowPort,
{
}
fn _assert_impl_federation_follow_request_port()
where
crate::service::ActivityPubService: domain::ports::FederationFollowRequestPort,
{
}
fn _assert_impl_federation_fetch_port()
where
crate::service::ActivityPubService: domain::ports::FederationFetchPort,
{
}
fn _assert_impl_federation_action_port()
where where
crate::service::ActivityPubService: domain::ports::FederationActionPort, crate::service::ActivityPubService: domain::ports::FederationActionPort,
{ {

View File

@@ -6,8 +6,9 @@ use domain::{
remote_actor::RemoteActor, remote_actor::RemoteActor,
}, },
ports::{ ports::{
ActivityPubRepository, EventPublisher, FederationActionPort, FederationSchedulerPort, ActivityPubRepository, EventPublisher, FederationActionPort, FederationFollowPort,
FeedRepository, FollowRepository, RemoteActorConnectionRepository, UserReader, FederationFollowRequestPort, FederationSchedulerPort, FeedRepository, FollowRepository,
RemoteActorConnectionRepository, UserReader,
}, },
value_objects::UserId, value_objects::UserId,
}; };
@@ -15,14 +16,14 @@ use domain::{
use super::social; use super::social;
pub async fn list_pending_requests( pub async fn list_pending_requests(
federation: &dyn FederationActionPort, federation: &dyn FederationFollowRequestPort,
user_id: &UserId, user_id: &UserId,
) -> Result<Vec<RemoteActor>, DomainError> { ) -> Result<Vec<RemoteActor>, DomainError> {
federation.get_pending_followers(user_id).await federation.get_pending_followers(user_id).await
} }
pub async fn accept_follow_request( pub async fn accept_follow_request(
federation: &dyn FederationActionPort, federation: &dyn FederationFollowRequestPort,
user_id: &UserId, user_id: &UserId,
actor_url: &str, actor_url: &str,
) -> Result<(), DomainError> { ) -> Result<(), DomainError> {
@@ -30,7 +31,7 @@ pub async fn accept_follow_request(
} }
pub async fn reject_follow_request( pub async fn reject_follow_request(
federation: &dyn FederationActionPort, federation: &dyn FederationFollowRequestPort,
user_id: &UserId, user_id: &UserId,
actor_url: &str, actor_url: &str,
) -> Result<(), DomainError> { ) -> Result<(), DomainError> {
@@ -38,14 +39,14 @@ pub async fn reject_follow_request(
} }
pub async fn list_remote_followers( pub async fn list_remote_followers(
federation: &dyn FederationActionPort, federation: &dyn FederationFollowRequestPort,
user_id: &UserId, user_id: &UserId,
) -> Result<Vec<RemoteActor>, DomainError> { ) -> Result<Vec<RemoteActor>, DomainError> {
federation.get_remote_followers(user_id).await federation.get_remote_followers(user_id).await
} }
pub async fn remove_remote_follower( pub async fn remove_remote_follower(
federation: &dyn FederationActionPort, federation: &dyn FederationFollowRequestPort,
user_id: &UserId, user_id: &UserId,
actor_url: &str, actor_url: &str,
) -> Result<(), DomainError> { ) -> Result<(), DomainError> {
@@ -53,7 +54,7 @@ pub async fn remove_remote_follower(
} }
pub async fn list_remote_following( pub async fn list_remote_following(
federation: &dyn FederationActionPort, federation: &dyn FederationFollowPort,
user_id: &UserId, user_id: &UserId,
) -> Result<Vec<RemoteActor>, DomainError> { ) -> Result<Vec<RemoteActor>, DomainError> {
federation.get_remote_following(user_id).await federation.get_remote_following(user_id).await
@@ -62,7 +63,7 @@ pub async fn list_remote_following(
pub async fn remove_remote_following( pub async fn remove_remote_following(
follows: &dyn FollowRepository, follows: &dyn FollowRepository,
users: &dyn UserReader, users: &dyn UserReader,
federation: &dyn FederationActionPort, federation: &dyn FederationFollowPort,
events: &dyn EventPublisher, events: &dyn EventPublisher,
user_id: &UserId, user_id: &UserId,
handle: &str, handle: &str,

View File

@@ -4,7 +4,7 @@ use domain::{
events::DomainEvent, events::DomainEvent,
models::social::{Block, Boost, Follow, FollowState, Like}, models::social::{Block, Boost, Follow, FollowState, Like},
ports::{ ports::{
BlockRepository, BoostRepository, EventPublisher, FederationActionPort, FollowRepository, BlockRepository, BoostRepository, EventPublisher, FederationFollowPort, FollowRepository,
LikeRepository, UserReader, LikeRepository, UserReader,
}, },
value_objects::{BoostId, LikeId, ThoughtId, UserId, Username}, value_objects::{BoostId, LikeId, ThoughtId, UserId, Username},
@@ -93,7 +93,7 @@ pub async fn unboost_thought(
pub async fn follow_actor( pub async fn follow_actor(
follows: &dyn FollowRepository, follows: &dyn FollowRepository,
users: &dyn UserReader, users: &dyn UserReader,
federation: &dyn FederationActionPort, federation: &dyn FederationFollowPort,
events: &dyn EventPublisher, events: &dyn EventPublisher,
follower_id: &UserId, follower_id: &UserId,
username: &str, username: &str,
@@ -140,7 +140,7 @@ pub async fn follow_user(
pub async fn unfollow_actor( pub async fn unfollow_actor(
follows: &dyn FollowRepository, follows: &dyn FollowRepository,
users: &dyn UserReader, users: &dyn UserReader,
federation: &dyn FederationActionPort, federation: &dyn FederationFollowPort,
events: &dyn EventPublisher, events: &dyn EventPublisher,
follower_id: &UserId, follower_id: &UserId,
username: &str, username: &str,

View File

@@ -230,14 +230,35 @@ pub trait RemoteActorConnectionRepository: Send + Sync {
} }
#[async_trait] #[async_trait]
pub trait FederationActionPort: Send + Sync { pub trait FederationLookupPort: Send + Sync {
async fn lookup_actor(&self, handle: &str) -> Result<RemoteActor, DomainError>; async fn lookup_actor(&self, handle: &str) -> Result<RemoteActor, DomainError>;
async fn actor_json(&self, user_id: &UserId) -> Result<String, DomainError>;
async fn followers_collection_json(
&self,
user_id: &UserId,
page: Option<u32>,
) -> Result<String, DomainError>;
async fn following_collection_json(
&self,
user_id: &UserId,
page: Option<u32>,
) -> Result<String, DomainError>;
}
#[async_trait]
pub trait FederationFollowPort: Send + Sync {
async fn follow_remote(&self, local_user_id: &UserId, handle: &str) -> Result<(), DomainError>; async fn follow_remote(&self, local_user_id: &UserId, handle: &str) -> Result<(), DomainError>;
async fn unfollow_remote( async fn unfollow_remote(
&self, &self,
local_user_id: &UserId, local_user_id: &UserId,
handle: &str, handle: &str,
) -> Result<(), DomainError>; ) -> Result<(), DomainError>;
async fn get_remote_following(&self, user_id: &UserId)
-> Result<Vec<RemoteActor>, DomainError>;
}
#[async_trait]
pub trait FederationFollowRequestPort: Send + Sync {
async fn get_pending_followers( async fn get_pending_followers(
&self, &self,
user_id: &UserId, user_id: &UserId,
@@ -259,36 +280,38 @@ pub trait FederationActionPort: Send + Sync {
user_id: &UserId, user_id: &UserId,
actor_url: &str, actor_url: &str,
) -> Result<(), DomainError>; ) -> Result<(), DomainError>;
async fn get_remote_following(&self, user_id: &UserId) }
-> Result<Vec<RemoteActor>, DomainError>;
async fn actor_json(&self, user_id: &UserId) -> Result<String, DomainError>; #[async_trait]
async fn followers_collection_json( pub trait FederationFetchPort: Send + Sync {
&self,
user_id: &UserId,
page: Option<u32>,
) -> Result<String, DomainError>;
async fn following_collection_json(
&self,
user_id: &UserId,
page: Option<u32>,
) -> Result<String, DomainError>;
async fn fetch_outbox_page( async fn fetch_outbox_page(
&self, &self,
outbox_url: &str, outbox_url: &str,
page: u32, page: u32,
) -> Result<Vec<crate::models::remote_note::RemoteNote>, DomainError>; ) -> Result<Vec<crate::models::remote_note::RemoteNote>, DomainError>;
async fn fetch_actor_urls_from_collection( async fn fetch_actor_urls_from_collection(
&self, &self,
collection_url: &str, collection_url: &str,
) -> Result<Vec<String>, DomainError>; ) -> Result<Vec<String>, DomainError>;
async fn resolve_actor_profiles( async fn resolve_actor_profiles(
&self, &self,
urls: Vec<String>, urls: Vec<String>,
) -> Vec<crate::models::actor_connection_summary::ActorConnectionSummary>; ) -> Vec<crate::models::actor_connection_summary::ActorConnectionSummary>;
} }
pub trait FederationActionPort:
FederationLookupPort + FederationFollowPort + FederationFollowRequestPort + FederationFetchPort
{
}
impl<
T: FederationLookupPort
+ FederationFollowPort
+ FederationFollowRequestPort
+ FederationFetchPort,
> FederationActionPort for T
{
}
#[async_trait] #[async_trait]
pub trait FeedRepository: Send + Sync { pub trait FeedRepository: Send + Sync {
async fn home_feed( async fn home_feed(

View File

@@ -555,11 +555,34 @@ impl RemoteActorRepository for TestStore {
} }
#[async_trait] #[async_trait]
impl FederationActionPort for TestStore { impl FederationLookupPort for TestStore {
async fn lookup_actor(&self, _handle: &str) -> Result<RemoteActor, DomainError> { async fn lookup_actor(&self, _handle: &str) -> Result<RemoteActor, DomainError> {
Err(DomainError::NotFound) Err(DomainError::NotFound)
} }
async fn actor_json(&self, _user_id: &UserId) -> Result<String, DomainError> {
Err(DomainError::NotFound)
}
async fn followers_collection_json(
&self,
_user_id: &UserId,
_page: Option<u32>,
) -> Result<String, DomainError> {
Err(DomainError::NotFound)
}
async fn following_collection_json(
&self,
_user_id: &UserId,
_page: Option<u32>,
) -> Result<String, DomainError> {
Err(DomainError::NotFound)
}
}
#[async_trait]
impl FederationFollowPort for TestStore {
async fn follow_remote( async fn follow_remote(
&self, &self,
_local_user_id: &UserId, _local_user_id: &UserId,
@@ -576,6 +599,16 @@ impl FederationActionPort for TestStore {
Ok(()) Ok(())
} }
async fn get_remote_following(
&self,
_user_id: &UserId,
) -> Result<Vec<RemoteActor>, DomainError> {
Ok(vec![])
}
}
#[async_trait]
impl FederationFollowRequestPort for TestStore {
async fn get_pending_followers( async fn get_pending_followers(
&self, &self,
_user_id: &UserId, _user_id: &UserId,
@@ -613,34 +646,10 @@ impl FederationActionPort for TestStore {
) -> Result<(), DomainError> { ) -> Result<(), DomainError> {
Ok(()) Ok(())
} }
}
async fn get_remote_following( #[async_trait]
&self, impl FederationFetchPort for TestStore {
_user_id: &UserId,
) -> Result<Vec<RemoteActor>, DomainError> {
Ok(vec![])
}
async fn actor_json(&self, _user_id: &UserId) -> Result<String, DomainError> {
Err(DomainError::NotFound)
}
async fn followers_collection_json(
&self,
_user_id: &UserId,
_page: Option<u32>,
) -> Result<String, DomainError> {
Err(DomainError::NotFound)
}
async fn following_collection_json(
&self,
_user_id: &UserId,
_page: Option<u32>,
) -> Result<String, DomainError> {
Err(DomainError::NotFound)
}
async fn fetch_outbox_page( async fn fetch_outbox_page(
&self, &self,
_outbox_url: &str, _outbox_url: &str,