写点什么

Helidon 项目教程:如何使用 Oracle 轻量级 Java 框架构建微服务

2020 年 7 月 21 日

Helidon项目教程:如何使用Oracle轻量级Java框架构建微服务

本文要点:


  • Helidon 是由 Oracle 在 2018 年 9 月份推出的轻量级微服务框架。

  • Helidon 是一个创建微服务应用的 Java 库的集合。

  • 按照设计,Helidon 非常简单和快捷,它提供了两个版本:Helidon SE 和 Helidon MP。

  • Helidon 支持 GraalVM,能够将 Helidon SE 应用转换为原生可执行的代码。

  • 在本教程中,我们将会向你介绍 Helidon,探索 Helidon SE 和 Helidon MP,并且会下载本教程相关的 GitHub 仓库内容。

  • Helidon 1.4.4 是当前的稳定版本,不过 Helidon 2.0 计划在今年发布。


2018 年 9 月份,Oracle 推出了新的开源框架Helidon项目。Helidon 最初的名字叫做 J4C(Java for Cloud),它是一个创建基于微服务应用的 Java 库的集合。在推出六个月之后,Helidon 1.0 于 2019 年 2 月份发布。目前的稳定发布版本是 Helidon 1.4.4,但是 Oracle 正在计划发布 Helidon 2.0(2.0 版本已经在 6 月 25 日发布,参见发布声明变更记录——译者注)。


本教程将会介绍 Helidon SE 和 Helidon MP,探索 Helidon SE 的三个核心组件、如何起步并且还会介绍一个基于 Helidon MP 构建的电影应用。另外,我们还有关于 GraalVM 的讨论以及在即将发布的 Helidon 2.0 中都有哪些值得期待的功能。


Helidon 概览

按照设计,Helidon 非常简单和快捷,它很独特的一点在于它提供了两个编程模型 Helidon SE Helidon MP。在下图中,展示与其他流行的微服务框架对比,Helidon SE 和 Helidon MP 分别位于什么地方。



Helidon SE

Helidon SE 是一个微框架,它提供了创建微服务的三个核心组件来构建基于微服务的应用,即 Web 服务器、配置以及安全性。它是一个很小的函数式 API,具有反应式(reactive)、简单和透明的特点,不需要应用服务器。


我们通过一个简单的例子看一下函数式风格的 Helidon SE,这里使用WebServer接口启动了一个 Helidon Web 服务器:


WebServer.create(    Routing.builder()        .get("/greet", (req, res)             -> res.send("Hello World!"))        .build())    .start();

复制代码


以该例子作为起点,我们将会增量式地构建一个正式的startServer()方法,以此探索 Helidon SE 的三个核心组件,它是你所下载的服务器应用的一部分。


Web 服务器组件

受到 NodeJS 和其他 Java 框架灵感的启发,Helidon 的 web 服务器组件是一个运行在Netty上的异步反应式 API。WebServer接口提供了基本的服务器生命周期和监控,可以通过配置、路由、错误处理以及构建度量指标和健康端点来进行增强。


我们从startServer()方法的第一个版本开始,它会在一个随机可用的端口上启动 Helidon web 服务器:


private static void startServer() {    Routing routing = Routing.builder()            .any((request, response) -> response.send("Greetings from the web server!" + "\n"))            .build();
WebServer webServer = WebServer .create(routing) .start() .toCompletableFuture() .get(10, TimeUnit.SECONDS);
System.out.println("INFO: Server started at: http://localhost:" + webServer.port() + "\n"); }
复制代码


首先,我们需要构建一个Routing接口的实例,它会作为一个具有路由规则的 HTTP 请求-响应处理器。在本例中,我们使用any()方法将请求路由至定义好的服务器响应”Greetings from the web server!“,这个响应信息会通过浏览器或者curl命令展示出来。


在构建 web 服务器的时候,我们调用了重载的create()方法。按照设计,该方法用来接受各种服务器配置。如上所示,最简单的方式就是接受我们刚刚创建的用来提供默认服务器配置的实例变量routing


按照设计,Helidon Web 服务器是反应式的,这意味着start()方法会返回一个CompletionStage<WebServer>接口的实例来启动 Web 服务器。它允许我们调用toCompletableFuture()方法。因为这里没有指定服务器端口,服务器在启动的时候会选择任意一个可用的端口。


接下来,我们使用 Maven 构建并运行我们的服务器应用:


$ mvn clean package$ java -jar target/helidon-server.jar
复制代码


服务器启动的时候,我们会在终端窗口看到如下所示的输出:


Apr 15, 2020 1:14:46 PM io.helidon.webserver.NettyWebServer &lt;init&gt;INFO: Version: 1.4.4Apr 15, 2020 1:14:46 PM io.helidon.webserver.NettyWebServer lambda$start$8INFO: Channel '@default' started: [id: 0xcba440a6, L:/0:0:0:0:0:0:0:0:52535]INFO: Server started at: http://localhost:52535
复制代码


如最后一行所示,Helidon web 服务器选择了 52535 端口。在服务器运行的时候,在浏览器输入这个 URL 或者在单独的终端窗口执行如下的curl命令:


$ curl -X GET http://localhost:52535


我们将会看到“Greetings from the web server!


要关闭 web 服务器,我们只需要添加如下这行代码:


webServer.shutdown()        .thenRun(() -> System.out.println("INFO: Server is shutting down...Good bye!"))        .toCompletableFuture();
复制代码


配置组件

配置组件会加载和处理配置属性。Helidon 的Config接口将会从预先定义的配置文件中读取配置属性,配置文件通常是YAML格式,但是并不限于此。


我们创建一个application.yaml文件,它提供了应用、服务器和安全性方面的配置。


app:  greeting: "Greetings from the web server!"
server: port: 8080 host: 0.0.0.0
security: config: require-encryption: false
providers: - http-basic-auth: realm: "helidon" users: - login: "ben" password: "${CLEAR=password}" roles: ["user", "admin"] - login: "mike" password: "${CLEAR=password}" roles: ["user"] - http-digest-auth:
复制代码


application.yaml文件中有三个主要的组成部分或者说是节点,即appserversecurity。前两个节点非常简单直接。greeting子节点定义了我们在上述样例中硬编码的服务器响应。port子节点定义了 web 服务器在启动的时候所使用的端点是 8080。但是,你应该也注意到了,security节点要复杂一些,它使用 YAML 的映射序列定义了多个条目。通过使用“-”字符分割,我们定义了两个安全 provider(即http-basic-authhttp-digest-auth)和两个用户(即benmike)。在本教程的安全组件章节,我们将会对其进行详细讨论。


另外,这个配置允许我们通过将config.require-encryption设置为false以使用明文密码。在生产环境中,我们显然需要将这个值设置为true,这样的话试图传入明文密码会抛出异常。


现在,基于这个可用的配置文件,我们就可以更新startServer()方法来使用刚刚定义的配置。


private static void startServer() {    Config config = Config.create();    ServerConfiguration serverConfig = ServerConfiguration.create(config.get("server"));
Routing routing = Routing.builder() .any((request, response) -> response.send(config.get("app.greeting").asString().get() + "\n")) .build();
WebServer webServer = WebServer .create(serverConfig, routing) .start() .toCompletableFuture() .get(10, TimeUnit.SECONDS);
System.out.println("INFO: Server started at: http://localhost:" + webServer.port() + "\n"); }
复制代码


首先,我们需要通过调用Configcreate()方法构建该接口的一个实例。Config提供的get(String key)方法能够返回配置文件中给定key所声明的节点或子节点。例如,config.get("server")将会返回server节点下的内容,config.get("app.greeting")将会返回“Greetings from the web server!”。


接下来,我们创建了ServerConfiguration实例并为其提供不可变的 web 服务器信息,这是通过调用其create()方法并传入 config.get("server")语句实现的。


实例变量routing的构造方式和之前的样例很相似,只不过我们消除了硬编码的服务器响应,将其替换为调用 config.get("app.greeting").asString().get()。


Web 服务器的创建过程和之前的样例类似,只不过我们使用了一个不同版本的create()方法,它接受两个实例变量serverConfigrouting


我们可以使用相同的 Maven 和 Java 命令来构建和运行这个版本的 Web 服务器应用。执行相同的curl命令:


$ curl -X GET http://localhost:8080


你应该会看到“Greetings from the web server!


安全组件

Helidon 的安全组件提供了认证、授权、审计和出站安全性功能。在 Helidon 应用中,支持使用大量的安全供应商实现:


  • HTTP Basic 认证

  • HTTP Digest 认证

  • HTTP 签名

  • 基于属性的访问控制(Attribute Based Access Control,ABAC)授权

  • JWT Provider

  • Header 断言

  • Google 登录认证

  • OpenID Connect

  • IDCS 角色映射(IDCS Role Mapping)


在 Helidon 应用中,我们可以采用如下三种方式的一种来实现安全性:


  • 手动提供配置的构建者模式

  • 提供配置文件的配置模式

  • 组合使用构建者模式和配置模式的混合模式


在样例应用中,我们将会采用混合方式,但是我们首先要做一些准备工作。


我们看一下如何引用在配置文件中 security 节点下所定义的用户。考虑如下的字符串:


security.providers.0.http-basic-auth.users.0.login


当解析器遇到字符串中的数字时,就意味着配置文件中有一个或多个子节点。在本例中,providers后面的0将会指导解析器转移至第一个 provider 子节点,即http-basic-authusers后面的0将会指导解析器转移至包含loginpasswordroles的第一个 user 子节点。因此,当传递到config.get()方法时,上述的字符串将会返回ben用户的 login、password 和 role 信息。与之类似,mike用户的 login、password 和 role 信息可以通过如下的字符串获取到:


security.providers.0.http-basic-auth.users.1.login


接下来,我们为 Web 服务器应用创建一个新的类AppUser,它实现了SecureUserStore.User接口:


public class AppUser implements SecureUserStore.User {
private String login; private char[] password; private Collection<String> roles;
public AppUser(String login, char[] password, Collection<String> roles) { this.login = login; this.password = password; this.roles = roles; }
@Override public String login() { return login; }
@Override public boolean isPasswordValid(char[] chars) { return false; }
@Override public Collection<String> roles() { return roles; }
@Override public Optional<String> digestHa1(String realm, HttpDigest.Algorithm algorithm) { return Optional.empty(); } }
复制代码


我们将会使用这个类来构建角色和用户的 map,如下所示:


Map<String, AppUser> users = new HashMap<>();
复制代码


为了实现这一点,我们为 Web 服务器应用添加了一个新的方法getUsers(),它会使用配置文件中http-basic-auth子元素的配置来填充这个 map。


private static Map<String, AppUser> getUsers(Config config) {    Map<String, AppUser> users = new HashMap<>();
ConfigValue<String> ben = config.get("security.providers.0.http-basic-auth.users.0.login").asString(); ConfigValue<String> benPassword = config.get("security.providers.0.http-basic-auth.users.0.password").asString(); ConfigValue<List<Config>> benRoles = config.get("security.providers.0.http-basic-auth.users.0.roles").asNodeList();
ConfigValue<String> mike = config.get("security.providers.0.http-basic-auth.users.1.login").asString(); ConfigValue<String> mikePassword = config.get("security.providers.0.http-basic-auth.users.1.password").asString(); ConfigValue<List<Config>> mikeRoles = config.get("security.providers.0.http-basic-auth.users.1.roles").asNodeList();
users.put("admin", new AppUser(ben.get(), benPassword.get().toCharArray(), Arrays.asList("user", "admin"))); users.put("user", new AppUser(mike.get(), mikePassword.get().toCharArray(), Arrays.asList("user")));
return users; }
复制代码


我们为 Web 服务器应用准备好了这个新功能,接下来,我们更新startServer()方法,使用 Helidon 的 HTTP Basic 认证实现来为其添加安全性:


private static void startServer() {    Config config = Config.create();    ServerConfiguration serverConfig = ServerConfiguration.create(config.get("server"));
Map<String, AppUser> users = getUsers(config); displayAuthorizedUsers(users);
SecureUserStore store = user -> Optional.ofNullable(users.get(user));
HttpBasicAuthProvider provider = HttpBasicAuthProvider.builder() .realm(config.get("security.providers.0.http-basic-auth.realm").asString().get()) .subjectType(SubjectType.USER) .userStore(store) .build();
Security security = Security.builder() .config(config.get("security")) .addAuthenticationProvider(provider) .build();
WebSecurity webSecurity = WebSecurity.create(security) .securityDefaults(WebSecurity.authenticate());
Routing routing = Routing.builder() .register(webSecurity) .get("/", (request, response) -> response.send(config.get("app.greeting").asString().get() + "\n")) .get("/admin", (request, response) -> response.send("Greetings from the admin, " + users.get("admin").login() + "!\n")) .get("/user", (request, response) -> response.send("Greetings from the user, " + users.get("user").login() + "!\n")) .build();
WebServer webServer = WebServer .create(serverConfig, routing) .start() .toCompletableFuture() .get(10, TimeUnit.SECONDS);
System.out.println("INFO: Server started at: http://localhost:" + webServer.port() + "\n"); }
复制代码


和前面样例做法一样,我们构建了变量实例configserverConfig。随后,我们通过上述的getUsers()方法构建了角色和用户的 map。


这里利用了Optional的空类型安全性,store实例变量是通过SecureUserStore接口构建的,如 lambda 表达式所示。SecureUserStore 同时用于 HTTP Basic 认证和 HTTP Digest 认证。需要注意,HTTP Basic 可能是非安全的,即便使用 SSL 也是如此,因为密码并不是必需的。


我们现在已经准备好构建HTTPBasicAuthProvider实例了,它是SecurityProvider接口的一个实现类。realm()方法定义了在未认证的时候发送至浏览器(或其他客户端)的安全 realm 名。因为我们在配置文件中定义了一个 realm,所以我们将它传递到了该方法中。subjectType()方法定义了安全 provider 抽取或传播的 principal 类型。它会接受SubjectType枚举的两个值中的一个,也就是USERSERVICEuserStore()方法接受我们刚刚构建的store实例变量,用来在我们的应用中校验用户。


借助provider实例变量,我们现在就可以构建Security类的实例了,用来启动安全功能并将它与其他框架进行集成。我们使用config()addAuthenticationProvider()来完成这一点。需要注意的是,我们可以注册多个安全 provider,只需要通过addAuthenticationProvider()方法将它们链接在一起即可。例如,假设我们定义了实例变量basicProviderdigestProvider,它们分别代表HttpBasicAuthProviderHttpDigestAuthProvider类,那么我们的security实例变量可以按照如下的方式进行构建:


Security security = Security.builder()        .config(config.get("security"))        .addAuthenticationProvider(basicProvider)        .addAuthenticationProvider(digestProvider)        .build();
复制代码


WebSecurity类实现了Service接口,它封装了一组路由规则和相关逻辑。实例变量webSecurity是通过create()方法和securityDefaults()方法构建的,前者将security 实例变量传递了进去而后者则将WebSecurity.authentic()传递了进去,从而确保请求将会经过认证过程。


我们熟悉的实例变量routing并没有太大的差异,在前面两个样例中我们已经构建过它。它注册了webSecurity实例变量并定义了端点“/”、“/admin”和“/user”,这是通过get()方法将它们链接起来的。注意,/admin/user端点分别关联了benmike


最后,我们的 web 服务器就可以启动了!在实现了所有的零部件之后,构建 web 服务器就和之前的样例完全一样了。


现在,我们可以使用相同的 Maven 和 Java 命令构建和运行 web 服务器应用了,执行如下的curl命令:


$ curl -X GET [http://localhost:8080/](http://localhost:8080/)将会返回“Greetings from the web server!


$ curl -X GET [http://localhost:8080/admin](http://localhost:8080/admin)将会返回“Greetings from the admin, ben!


$ curl -X GET [http://localhost:8080/user](http://localhost:8080/user)将会返回“Greetings from the user, mike!


你可以看到阐述所有三个版本startServer()方法的综合服务器应用,它们关联了我们刚刚探讨的三个 Helidon SE 核心组件。同时,你也可以参考更广泛的安全样例,它们会为你展示如何实现其他的安全 provider。


Helidon MP

Helidon MP 构建在 Helidon SE 之上,是一个小型的、声明式风格的 API,它是MicroProfile规范的实现,MicroProfile是一个平台,致力于将企业级 Java 优化为微服务架构,适用于构建基于微服务的应用。MicroProfile 最初是由 IBM、Red Hat、Payara 和 Tomitribe 在 2006 年联合成立的,当时它定义了最初的三个 API,即 CDI (JSR 365)、JSON-P (JSR 374)和 JAX-RS (JSR-370),它们被认为是构建微服务应用所需的最小数量的 API。从那时开始,MicroProfile 已经发展到 12 个核心 API 以及支持反应式流和 GraphQL 的 4 个独立 API。MicroProfile 3.3 于 2020 年 2 月发布,是最新的版本。


Helidon MP 目前支持 MicroProfile 3.2。对于 Java EE/Jakarta EE 开发人员来说,Helidon MP 是一个非常好的可选方案,因为它使用注解实现了熟悉的声明式方式。它不需要特殊的部署模型,也不需要额外的 Java EE 打包。


我们看一下 Helidon MP 的声明式风格,这是一个启动 Helidon web 的简单样例,可以将其与 Helidon SE 的函数式风格进行一下对比。


public class GreetService {  @GET  @Path("/greet")  public String getMsg() {    return "Hello World!";    }  }
复制代码


请注意这种风格与 Helidon SE 函数式风格的差异。


Helidon 架构

既然已经介绍了 Helidon SE 和 Helidon MP,那么我们看一下它们是如何组合在一起的。Helidon 的架构如下图所示。Helidon MP 是构建在 Helidon SE 和 CDI 扩展之上的,如下文所述,CDI 扩展丰富了 Helidon MP 的云原生能力。



CDI Extensions

Helidon 提供了可迁移的上下文与依赖注入(Context and Dependency Injection,CDI)扩展,它支持与各种数据源、事务和客户端进行集成,扩展 Helidon MP 应用的云原生功能。目前它提供了如下的扩展:



Helidon 快速上手指南

Helidon 为Helidon SEHelidon MP都提供了快速上手指南。我们只需要访问这些页面并遵循指令即可。例如,我们可以通过在终端窗口执行如下的 Maven 命令就能快速构建一个 Helidon SE 应用:


$ mvn archetype:generate -DinteractiveMode=false \    -DarchetypeGroupId=io.helidon.archetypes \    -DarchetypeArtifactId=helidon-quickstart-se \    -DarchetypeVersion=1.4.4 \    -DgroupId=io.helidon.examples \    -DartifactId=helidon-quickstart-se \    -Dpackage=io.helidon.examples.quickstart.se
复制代码


这将会在helidon-quickstart-se目录下生成一个小型的但是可运行的应用,它包含了测试和各种配置文件,这些配置文件用于应用(application.yaml)、日志(logging.properties)、使用 GraalVM 构建原生镜像(native-image.properties)、使用 Docker 容器化应用(DockerfileDockerfile.native)以及使用 Kubernetes 进行应用编排(app.yaml)。


类似地,我们可以快速构建 Helidon MP 应用:


$ mvn archetype:generate -DinteractiveMode=false \    -DarchetypeGroupId=io.helidon.archetypes \    -DarchetypeArtifactId=helidon-quickstart-mp \    -DarchetypeVersion=1.4.4 \    -DgroupId=io.helidon.examples \    -DartifactId=helidon-quickstart-mp \    -Dpackage=io.helidon.examples.quickstart.mp
复制代码


对于构建复杂的应用来讲,这是一个很好的起点,我们会在下一节讨论一个复杂的应用。


Movie 应用

基于所生成的 Helidon MP 快速上手应用,我们添加了一些额外的类完成了movie应用,新增加的类包括 POJO、资源、repository、自定义异常以及ExceptionMapper的实现,该应用会维护 Quentin Tarantino 电影的一个列表。HelidonApplication类如下所示,它会注册所需的类。


@ApplicationScoped@ApplicationPath("/")public class HelidonApplication extends Application {
@Override public Set<Class<?>> getClasses() { Set<Class<?>> set = new HashSet<>(); set.add(MovieResource.class); set.add(MovieNotFoundExceptionMapper.class); return Collections.unmodifiableSet(set); } }
复制代码


你可以 colne GitHub 仓库以了解关于该应用的详细信息。


GraalVM

Helidon 支持GraalVM,它是一个多语言的虚拟机和平台,能够将应用转换成原生可执行代码。GraalVM 是由Oracle Labs创建的,由GraalSubstrateVMTruffle组成,其中 Graal 是一个使用 Java 编写的即时编译器,SubstrateVM 是一个允许提前将 Java 应用编译为可执行镜像的框架,Truffle 则是一个用于构建语言解释器(interpreter)的开源工具集和 API。它最新的版本是 20.1.0。


我们可以通过 GraalVM 的native-image工具将 Helidon SE 应用转换成原生可执行代码,native-image需要通过 GraalVM 的gu工具单独进行安装:


$ gu install native-image$ exportGRAALVM_HOME=/usr/local/bin/graalvm-ce-java11-20.1.0/Contents/Home
复制代码


安装完成之后,我们就可以回到helidon-quickstart-se目录并执行如下的命令:


$ mvn package -Pnative-image


这个操作将会耗时几分钟,完成之后,我们的应用就转换成了原生代码。可执行文件位于/target目录下。


Helidon 2.0 的路线图

Helidon 2.0.0 计划在 2020 年发布(目前,该版本已经发布,读者可以参考该地址——译者注)。该版本重要的新特性包括为 Helidon MP 应用添加对 GraalVM 的支持、新的 Web Client 和DB Client组件、新的CLI工具以及独立 MicroProfile Reactive MessagingReactive Streams Operators API 的实现。


直到最近,由于用到了 CDI 2.0(JSR 365)的反射(这是 MicroProfile API 的一个核心 API),所以只有 Helidon SE 应用能够利用 GraalVM。但是,根据客户的需要,Helidon 2.0.0 将支持 Helidon MP 应用转换成原生镜像。Oracle 创建了一个示例应用为 Java 社区预览这个新特性。


为了补充原有的三个核心 Helidon SE API,即 Web 服务器、配置和安全性,新的Web Client API 完备了 Helidon SE 的特性集。通过构建WebClient接口的实例,我们能够处理对特定端点的 HTTP 请求和响应。和 Web Server API 一样,Web Client 也可以通过配置文件进行配置。


我们可以详细了解开发人员可以期待 Helidon 2.0.0 会带来哪些新功能。


作者简介:


Michael Redlich 是新泽西州克林顿市ExxonMobil研究与工程部门的高级研究技术人员(观点仅代表个人),在过去的 30 年里,他具有开发定制科学实验室和 web 应用的经验。他还曾经在 Ai-Logix, Inc.(现在是AudioCodes了)担任过技术支持工程师,为客户提供技术支持和开发电话应用程序。他的技术专长包括面向对象设计和分析、关系型数据库设计和开发、计算机安全、C/ C++、Java、Python 和其他编程/脚本语言。他最近的关注点包括MicroProfileJakarta EEHelidonMicronautMongoDB


原文链接:


Project Helidon Tutorial: Building Microservices with Oracle’s Lightweight Java Framework


2020 年 7 月 21 日 19:081717

评论

发布
暂无评论
发现更多内容

学习总结1

Wee权

架构师训练营 -week02- 总结

大刘

极客大学架构师训练营

架构师训练营第 1 期第二周学习总结

郑凯元

极客大学架构师训练营

设计模式

高兵

Architecture Phase1 Week2:HomeWork

phylony-lu

极客大学架构师训练营

【第二周】课后作业

云龙

极客大学架构师训练营

架构师训练营第二次作业

月殇

极客大学架构师训练营

架构师训练营第 1 期 - 第2周 - 学习总结

wgl

华为18级工程师十年之作,整整3625页互联网大厂面试题合集

云流

学习 程序员 Java 面试 架构师技能

作业一

泡泡

第 2 周 框架设计 腐败的代码

Pyr0man1ac

作业二

泡泡

第二周作业1

sean

Architecture Phase1 Week2:Framework Design

phylony-lu

极客大学架构师训练营

架构师训练营第 1 期第二周课后练习题

郑凯元

极客大学架构师训练营

Serverless 的收益与挑战 | 2020年度状态报告

donghui

Serverless

架构师训练营 - 学习笔记 - 第二周

徐时良

第二周 框架设计 学习笔记

应鹏

学习 极客大学架构师训练营

第二周作业2

sean

依赖倒置原则和接口隔离原则

garlic

极客大学架构师训练营

数据结构之线性表

C语言与CPP编程

c++ 数据结构 C语言 线性表 数据结构与算法

架构师训练营第二周总结

月殇

极客大学架构师训练营

架构师训练营 - 命题作业 - 第二周

徐时良

甲方日常 23

句子

生活 随笔杂谈 减肥

架构师训练营 -week02- 作业

大刘

极客大学架构师训练营

面向对象设计原则及框架案例

garlic

极客大学架构师训练营

极客时间架构 1 期:第 2 周框架设计 - 命题作业

Null

架构师训练营 2 期 - 第二周总结

Geek_no_one

极客大学架构师训练营

第二周作业及学习笔记

橘子皮嚼着不脆

架构一期二班-吴水金-第二课作业

吴水金

罗辑思维(得到APP)要上市了,你不知道的27件事

赵新龙

罗辑思维 IPO

Helidon项目教程:如何使用Oracle轻量级Java框架构建微服务-InfoQ