ホームページ  >  記事  >  ウェブフロントエンド  >  スケーラビリティへの道 (フロントエンドでサービスを管理する方法)。

スケーラビリティへの道 (フロントエンドでサービスを管理する方法)。

WBOY
WBOYオリジナル
2024-09-10 11:11:12433ブラウズ

はじめに: フロントエンドでのサービスの正しい管理の重要性

スケーラブルで読みやすい方法でサービスを管理する方法を知ることは、アプリケーションのパフォーマンスだけでなく、開発者の健康と幸福を維持するためにも非常に重要です。サービスは、API の呼び出し、データベースとの対話、さらには連絡先へのアクセスなどの電話権限の管理など、外部と通信するアプリケーションの一部です。これらのサービスを適切に管理すると、アプリケーションのスケーラビリティが確保され、将来的に問題が発生することはなくなります。

この記事では、リポジトリ パターンを使用してスケーラブルな方法でフロントエンド サービスを管理する方法を検討します。このアプローチにより、制御された明確な方法でサービスにアクセスできるようになり、API への呼び出しがカプセル化され、コードの再利用とその保守性が容易になります。

この記事では、このソリューションの実装方法、従うべき良い実践方法、コードのスケーラビリティと保守の容易さを確保するためにこのソリューションがどのように役立つかを説明します。

サービス管理の基本概念: DTO とアダプター

よく組織されたアーキテクチャでは、アプリケーションのさまざまな層がどのように構成されているかを理解することが重要です。基本層の 1 つはインフラストラクチャ層であり、外部と対話するサービスが管理されます。

このインフラストラクチャ層内では、DTO (データ転送オブジェクト) とアダプターという 2 つの重要な概念がよく登場します。

  • DTO (データ転送オブジェクト): DTO は、API またはデータベースの応答内のデータを表すインターフェイスです。これらは、外部サービスから受け取る情報が、アプリケーションが簡単に処理できる特定の形式に準拠していることを確認するために役立ちます。たとえば、DTO は、API から受け取るユーザー オブジェクトの構造を定義できます。

  • アダプター: アダプターは、DTO からアプリケーション ドメイン インターフェイスへ、またはその逆にデータを変換する機能です。つまり、受信したデータを翻訳したり、送信したデータを翻訳したりすることができます。こうすることで、将来受信する情報が変更された場合でも、アプリケーション全体ではなく、アダプターのみに注目すれば済みます。

DTO とアダプターを使用すると、インフラストラクチャ層が柔軟になり、アプリケーション ロジックに影響を与えることなく、外部サービスの変更に簡単に適応できるようになります。さらに、レイヤー間の明確な分離が維持され、各レイヤーが特定の責任を果たします。

私たちが受け取るデータの例:

Camino hacia la escalabilidad ( Cómo Gestionar Servicios en Frontend.

// getUserProfile.adapter.ts

const userProfileAdapter = (data: UserProfileDto): UserProfile => ({
    username: data.user_username,
    profilePicture: data.profile_picture,
    about: data.user_about_me,
})

export deafult userProfileAdapter;

送信するデータの例:

Camino hacia la escalabilidad ( Cómo Gestionar Servicios en Frontend.



リポジトリ パターン
リポジトリ パターンは、データ アクセス ロジックをアプリケーションやビジネス ロジックから分離するという考えに基づいています。これにより、アプリケーション内でエンティティが持つメソッドを一元化してカプセル化する方法が提供されます。

最初に、このエンティティが持つメソッドの定義を使用してリポジトリのインターフェイスを作成する必要があります。

// UserProfileRepository.model.ts

export interface IUserProfileRepository {
   getUserProfile: (id: string) => Promise<UserProfile>;
   createUserProfile: (payload: UserProfile) => Promise<void>;
}

そしてリポジトリの作成を続けます。

// userProfile.repository.ts

export default function userProfileRepository(): IUserProfileRepository {
  const url = `${BASE_URL}/profile`;

  return {
     getUserProfile: getProfileById(id: string) {
          try {
            const res = await axios.get<UserProfileDto>(`${url}/${id}`);             
            return userProfileAdapter (userDto);
          } catch(error){
            throw error;
          }           
     },
     createUserProfile: create(payload: UserProfile) {
          try {
            const body = createUserProfilAdapter(payload);
            await axios.post(`${url}/create`, payload);
          } catch(error) {
            throw error;
          }
     }
  }
}

このアプローチにより、API 呼び出しをカプセル化し、DTO 形式で受信したデータをアダプターを通じて内部形式に変換できます。

以下に、私たちが従うアーキテクチャまたは構造の図を示します。インフラストラクチャ層にはサービス、DTO、アダプターが含まれます。この構造により、ビジネス ロジックと外部データを明確に分離できます。

Camino hacia la escalabilidad ( Cómo Gestionar Servicios en Frontend.



エラー処理
エラー ハンドラーを作成することでコードをさらに改善できます。

// errorHandler.ts

export function errorHandler(error: unknown): void {
  if (axios.isAxiosError(error)) {
    if (error.response) {
      throw Error(`Error: ${error.response.status} - ${error.response.data}`);
    } else if (error.request) {
     throw Error("Error: No response received from server");
    } else {
      throw Error(`Error: ${error.message}`);
    }
  } else {
    throw Error("Error: An unexpected error occurred");
  }
}
// userProfile.repository.ts

import { errorHandler } from './errorHandler';

export default function userProfileRepository(): IUserProfileRepository {
  const url = `${BASE_URL}/profile`;

  return {
    async getUserProfile(id: string) {
      try {
        const res = await axios.get<UserProfileDto>(`${url}/${id}`);
        return userProfileAdapter(res.data);
      } catch (error) {
        errorHandler(error);
      }
    },
    async createUserProfile(payload: UserProfile) {
      try {
        const body = createUserProfileAdapter(payload);
        await axios.post(`${url}/create`, body);
      } catch (error) {
        errorHandler(error);
      }
    }
  }
}

Esto nos permite desacoplar la lógica de presentación de la lógica de negocio, asegurando que la capa de UI solo se encargue de manejar las respuestas y actualizar el estado de la aplicación.


Implementación de los servicios
Una vez que hemos encapsulado la lógica de acceso a datos dentro de nuestro repositorio, el siguiente paso es integrarlo con la interfaz de usuario (UI).

Es importante mencionar que no hay una única forma de implementar el patrón Repository. La elección de la implementación dependerá mucho de la arquitectura de tu aplicación y de qué tan fiel quieras a las definiciones de la misma. En mi experiencia, una de las formas más útiles y prácticas de integrarlo ha sido mediante el uso de hooks, el cual desarrollaremos a continuación.

export function useGetUserProfile(id: string) {
  const [data, setData] = useState<UserProfile | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const repository = userProfileRepository();

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const userProfile = await repository.getUserProfile(id);
        setData(userProfile);
      } catch (err) {
        setError((err as Error).message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [id]);

  return { data, loading, error };
}

interface UserProfileProps {
  id: string;
}

export function UserProfile({ id }: UserProfileProps) {
  const { data, loading, error } = useUserProfile(id);

  if (loading) return <Skeleton />;
  if (error) {
      toast({
        variant: "destructive",
        title: "Uh oh! Something went wrong.",
        description: error, 
      });
  }


  return (
    <div>
      <h1>{data?.username}</h1>
      <img src={data?.profilePicture} alt={`${data?.username}'s profile`} />
      <p>{data?.about}</p>
    </div>
  );
}

Ahora, el componente UserProfile no necesita saber nada sobre cómo se obtienen los datos del perfil, solo se encarga de mostrar los resultados o mensajes de error.

Esto se puede mejorar aún más si las necesidades lo requieren, por ejemplo con la utilizacion de react query dentro del hook para tener un mayor control sobre el cacheo y actualización de datos.

export function useGetUserProfile(id: string) {
  const repository = userProfileRepository();

  return useQuery({
    queryKey: ['userProfile', id], 
    queryFn: () => repository.getUserProfile(id), 
  });
 }



Conclusión

En este artículo, hemos explorado cómo implementar el patrón Repository en frontend, encapsulando las llamadas a las APIs y manteniendo una separación clara de responsabilidades mediante el uso de DTOs y adaptadores. Este enfoque no solo mejora la escalabilidad y el mantenimiento del código, sino que también facilita la actualización de la lógica de negocio sin tener que preocuparse por los detalles de la comunicación con los servicios externos. Además, al integrar React Query, obtenemos una capa extra de eficiencia en la gestión de datos, como el cacheo y la actualización automática.

Recuerda que no existe una manera única del manejo de servicios (por ejemplo también existe el patrón sagas para el manejo de side-effects). Lo importante es aplicar las buenas prácticas para asegurarnos de que nuestro código siga siendo flexible, limpio y fácil de mantener en el tiempo.

Espero que esta guía te haya sido útil para mejorar la manera en la que gestionas los servicios en tus proyectos frontend. Si te ha gustado, no dudes en dejar un comentario, darle me gusta o compartir el artículo para que más desarrolladores puedan beneficiarse. ¡Gracias por leer!

以上がスケーラビリティへの道 (フロントエンドでサービスを管理する方法)。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。