【问题标题】:What is a good architecture for an API client?API 客户端的良好架构是什么?
【发布时间】:2024-10-11 07:50:02
【问题描述】:

我将从头开始为 REST API 创建一个 PHP 客户端。我考虑使用Guzzle library

我认为用户不应该处理请求对象之类的传输(即使来自自定义 xxxRequest 类),而应该只处理业务对象/服务类。但也许我错了……

以下是一些可能的架构示例。哪些方面尊重良好做法,为什么?
随意改进它们或提出更好的方法。


示例 1:

class ApiClient {
    function createBooking (BookingEntity $booking, CustomerEntity $customer) {
        //...
    }
}

此示例允许用户仅使用业务对象,但如果 API 需要大量方法,则 ApiClient 类可能会开始变脏...此外,如果我们要添加功能,则必须修改 ApiClient。


示例 2:

class ApiClient {
    function execute(CreateBookingRequest $createBookingRequest) {
        //...
    }
}

class CreateBookingRequest {
    private BookingEntity $booking;
    private CustomerEntity $customer;
    private $queryParams;

    public createQueryParams() {
        // to create the query params (from the attibutes) for the http client
    }
}

这里如果需要一个新特性,ApiClient 不需要修改,只需要创建一个新类。但是,用户必须处理以 Request 后缀命名的类。 IMO 他对请求或技术问题一无所知。


示例 3:

class BookingService {
    function createBooking (BookingEntity $booking, CustomerEntity $customer) {
    //... 
    }
}

这个例子添加了一层。


在所有情况下,ApiClient/Service 类中的 Guzzle httpClient 属性应该在哪里?还是应该 ApiClient 类扩展 Guzzle Client 类?

【问题讨论】:

    标签: web-services guzzle api-design


    【解决方案1】:

    嗯,没有真正的标准,但这绝对是一个热门话题,我什至会说,它不是 Guzzle 特有的。

    简短回答:我会根据 API 的复杂性在选项 1 和 3 之间进行选择。

    更长的答案:如果有不同的资源(例如预订获取、创建、删除等),那么我会选择选项 3。如果只有几个 API 调用,那么我仍然会选择选项 3 , 因为从 API 的角度来看它是一个更好的表示(你的 API 客户端实际上只是 API 的一个实现,所以它应该遵循 API 的约定、术语......至少在理论上),但选项 1 可能更快并且一开始更容易理解。如果 API 变得更复杂,它的维护会变得很困难,所以即使那样最终您也必须将其重写为选项 3。

    配置的 Guzzle 实例转到您的类的构造函数。

    如果你有资源/服务类,你仍然可以有一个 API 客户端类,在这种情况下,它只不过是你的资源类的配置入口点和工厂。

    如果您计划在多个项目中重用此客户端,您可能需要考虑使用 HTTP 客户端抽象(例如 HTTPlug 而不是 Guzzle,因为您可能无法在所有项目中安装 Guzzle 6(依赖冲突,等)

    最后,但并非最不重要的一点是,您可能需要检查 this,这将成为这些情况的 API 基础/引导程序(但尚未完成,因此使用它需要您自担风险)

    【讨论】:

    • 非常有趣。如果有很多输入参数,你会建议什么?示例 2 允许将它们作为属性放在 xxxRequest 类中。由于使用对象而不是大量参数更好,我们如何在示例 1 和 3 中重现它?如果我们添加一个类,什么是好的命名约定?暂且说 xxxParam,适配器函数应该在哪里转换每个 xxxParam 属性以查询 http 客户端的参数(在本例中为 json 主体)?
    • 这完全取决于您的域。许多参数通常意味着某种数据结构,而不是完全无关紧要的参数。在那种情况下,我会将它们放入模型类中。我可以再次指出,客户端实际上是一个接口的实现,并且很可能 API 还公开了一个数据模型,您可以按原样使用它。序列化发生在客户端内部,它知道如何序列化模型。或者,您可以有一个单独的序列化层,但它仍会被客户端调用。
    • 好的,但是如果在 createBooking 方法中有很多模型对象作为输入参数,我认为将它们包装在一个对象中是一个好习惯,我错了吗?例如,假设我们有参数:pastebin.com/dBKJnk4S 我认为 createBooking 方法中的 9 个参数是不可接受的,所以我们必须包装它们,不是吗?有什么好的方法可以做到这一点?什么可以是这些类保持清晰的好后缀?将其转换为 json 主体的代码应该在哪里?
    • 我认为域应该是不言自明的。在这种情况下,我将分别拥有一个基于 JSON 的 Booking、BookingDetails 和 Customer 对象。最后两个是 Booking 的成员,应该传递给 API 客户端的 createBooking 方法。此方法将序列化对象(它自己进行序列化,或另一个序列化层)并处理将其发送到 API 等。