2

我们正在尝试编写一个 Java API 库来包装 Canvas LMS REST API。对于 API 处理的每种类型的对象,我们都有一个读取器和一个写入器接口。例如,我们有一个UserReader接口,它将从“ List users in course ”端点以及“ Search users in consortium ”端点返回用户对象。

我们正在努力解决的问题是如何最好地实现 API 调用的所有可选参数。如果您查看两个链接端点的规范,您会发现虽然它们都返回 User 对象,但它们采用非常不同的参数。最初的实现只是向 listUsersInCourse 方法添加了一堆列表(可以为空)和可选参数(可以为空)。因此,如果您不想指定任何可选参数,则调用此方法如下所示。是的,你可以有一个不指定任何选项的方法的无参数便捷版本,但是一旦你想使用任何选项,无论如何你都必须处理它们。

List<User> userList = userReader.listUsersInCourse("courseId", 
    Collections.emptyList(), Optional.empty(), Collections.emptyList());

这是一个糟糕的主意,因为 1)某些端点有很多选项,这会导致方法签名荒谬;2)如果 Canvas 添加或更改选项(这已经发生),它将破坏我们的方法签名并迫使每个依赖于我们的库的人更新他们的代码。

可选的命名参数会很好,但 Java 不会这样做。我查看了其他一些 API 库,似乎很少有这么多参数用于 REST API。我已经考虑过一些解决这个问题的方法,但我不确定最好的选择是什么。到目前为止,这是我想出的:

1

一种相对结构化的方法是遵循构建器模式,其中包含添加特定选项的方法。例如:

List<User> userList = userReader.withSearchTerm("Myname")
          .withEnrollmentType(EnrollmentType.Student)
          .withEnrollmentType(EnrollmentTye.Teacher)
          .listUsersInCourse("courseId");

这样,如果添加了新选项,它只是一种添加的新方法,不会破坏现有代码。不利的一面是,并非给定读取器类中的所有选项方法都适用于其中的所有 API 方法,这可能会造成混淆。例如,上面链接的两种用户列表方法都采用搜索词,但联盟不接受注册类型。因此,您总是需要仔细检查 Canvas API 文档以查看哪些选项对给定调用有效。此外,如果添加了一个选项,我们将不得不发布一个新版本的库,然后人们才能访问它。Canvas API 正在积极开发中,所以事情肯定会发生变化。

2

一种可能更加结构化的方法是创建特定于调用的选项类以进一步限制事物。这看起来像这样:

CourseUserListOptions opts = new CourseUserListOptions();
opts.addSearchTerm("Myname");
opts.addEnrollmentType(EnrollmentType.Student);
opts.addEnrollmentType(EnrollmentType.Teacher);
List<User> userList = userReader.listUsersInCourse("CourseId", opts);

会有另一个ConsortiumUserSearchOptions类没有addEnrollmentType方法。虽然这可能是最安全且定义最明确的方式,但编写和维护这似乎是一场噩梦。

3

另一种方法是使用更通用的addOption(String key, String value)方法。上面的调用会变成:

userReader.addOption("search_term", "Myname");
userReader.addOption("enrollment_type[]", EnrollmentType.Student.name());
userReader.addOption("enrollment_type[]", EnrollmentType.Teacher.name());
List<User> userList = userReader.listUsersInCourse("CourseId");

这将大大减少我们在编码和维护方面的工作量,但会迫使库的用户了解更多信息并传入 Canvas API 中定义的魔术字符串。正如上面 #1 中所指出的,结构较少的最大好处是,当 Canvas API 添加新选项时,我们的用户可以立即使用它,而不必等待包含新方法的库的新版本.

4

另一个选项也可以在每个 API 方法中只接受一个完全通用的选项列表,并强制库的用户更多地参与构建请求。这看起来像这样:

List<Pair> optsList = new ArrayList<>();
optsList.add(new ImmutablePair("search_term", "Myname"));
optsList.add(new ImmutablePair("enrollment_type[]", EnrollmentType.Student.name());
optsList.add(new ImmutablePair("enrollment_type[]", "EnrollmentType.Teacher.name());
List<User> userList = userReader.listUsersInCourse("courseId", optsList);

这些是我想出的选项。如果您使用此 API 库,您会发现哪个最有用?您更愿意编写/维护哪一个?我完全错过了更好的方法吗?

4

2 回答 2

0

如果我对您的理解正确,您的问题类似于“我应该考虑做多少工作来帮助我的用户”

听起来 Canvas API 很复杂且仍在不断变化,这将使我倾向于将负担推给用户,例如您的第四个选项。如果某些部分对用户来说太复杂而无法使用,这仍然留下了实施您的第二个选项的可能性。

于 2016-06-22T14:55:42.843 回答
0

您还可以使用客户端代理创建 JAX-RS 库。

定义一个接口,如

@Path("/api")
public interface CanvasAPI {

@GET
@Path("/v1/accounts/{accountId}/courses")
Response getCoursesForAccount(@PathParam("accountId") long accountId,
    @QueryParam("with_enrollments") boolean withEnrollments, @QueryParam("enrollment_term_id") long termId);

}

然后创建一个客户端代理

new ResteasyClientBuilder().build().target("<base_url>").proxy(CanvasAPI.class);

这为您提供了一个休息代理类,您可以像普通类一样使用它

CanvasAPI api = new ResteasyClientBuilder().build().target("<base_url>").proxy(CanvasAPI.class);
data = api.getCoursesForAccount(5l, true, 2l); 

这种方法很容易与 api 文档保持同步,因为您只需模仿规范中定义的调用并让代理为您构建所有内部工作。

于 2017-08-11T00:36:40.857 回答