阿里研究员谷朴:API 设计最佳实践的思考

API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素。但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多很多小的设计考量一点点叠加起来的(也即John Ousterhout老爷子说的Complexity is incremental【8】)。成功的系统不是有一些特别闪光的地方,而是设计时点点滴滴的努力积累起来的。

因此,这里我们试图思考并给出建议,一方面,什么样的API设计是__好__的设计?另一方面,在设计中如何能做到?

API设计面临的挑战千差万别,很难有处处适用的准则,所以在讨论原则和最佳实践时,无论这些原则和最佳实践是什么,一定有适应的场景和不适应的场景。因此我们在下面争取不仅提出一些建议,也尽量去分析这些建议在什么场景下适用,这样我们也可以有针对性的采取例外的策略。

范围

本文偏重于__一般性的API设计__,__并更适用于远程调用(RPC或者HTTP/RESTful的API)__,但是这里没有特别讨论RESTful API特有的一些问题。

另外,本文在讨论时,假定了客户端直接和远程服务端的API交互。在阿里,由于多种原因,通过客户端的SDK来间接访问远程服务的情况更多一些。这里并不讨论SDK带来的特殊问题,但是将SDK提供的方法看作远程API的代理,这里的讨论仍然适用。

API设计准则:什么是好的API

在这一部分,我们试图总结一些好的API应该拥有的特性,或者说是设计的原则。这里我们试图总结更加基础性的原则。所谓基础性的原则,是那些如果我们很好的遵守了就可以让API在之后演进的过程中避免多数设计问题的原则。

A good API

  • __提供清晰的思维模型 provides a good mental model__:API是用于程序之间的交互,但是一个API如何被使用,以及API本身如何被维护,是依赖于维护者和使用者能够对该API有清晰的、一致的认识。这种状况实际上是不容易达到的。
  • __简单 is simple__:“Make things as simple as possible, but no simpler.” 在实际的系统中,尤其是考虑到系统随着需求的增加不断的演化,我们绝大多数情况下见到的问题都是__过于复杂__的设计,而非过于简单,因此强调简单性一般是恰当的。
  • __容许多个实现 allows multiple implementations__:这个原则看上去更具体,但是这是我非常喜欢的一个原则。这是Sanjay Ghemawat常常提到的一个原则。一般来说,在讨论API设计时常常被提到的原则是解耦性原则或者说松耦合原则。然而相比于松耦合原则,这个原则更加有可操作性:如果一个API自身可以有多个__完全不同的实现__,一般来说这个API已经有了足够好的抽象,和自身的某一个具体实现无关,那么一般也不会出现和外部系统耦合过紧的问题。因此这个原则更本质一些。

最佳实践

本部分则试图讨论一些更加详细、具体的建议,可以让API的设计更容易满足前面描述的基础原则。

想想优秀的API例子:POSIX File API

如果说API的设计实践只能列一条的话,那么可能最有帮助的和最可操作的就是这一条。本文也可以叫做“通过File API体会API设计的最佳实践”。

所以整个最佳实践可以总结为一句话:“想想File API是怎么设计的。”

首先回顾一下File API的主要接口(以C为例,很多是Posix API,选用比较简单的I/O接口为例【1】:

int open(const char *path, int oflag, .../*,mode_t mode */);
int close (int filedes);
int remove( const char *fname );
ssize_t write(int fildes, const void *buf, size_t nbyte);
ssize_t read(int fildes, void *buf, size_t nbyte);

File API为什么是经典的好API设计?

  • File API已经有几十年历史(从1988年算起将近40年),尽管期间硬件软件系统的发展经历了好几代,这套API核心保持了稳定。这是极其了不起的。
  • API提供了非常清晰的概念模型,每个人都能够很快理解这套API背后的基础概念:什么是文件,以及相关联的操作(open, close, read, write),清晰明了;
  • 支持很多的不同文件系统实现,这些系统实现甚至于属于类型非常不同的设备,例如磁盘、块设备、管道(pipe)、共享内存、网络、终端terminal等等。这些设备有的是随机访问的,有的只支持顺序访问;有的是持久化的有的则不是。然而所有不同的设备不同的文件系统实现都可以采用了同样的接口,使得上层系统不必关注底层实现的不同,这是这套API强大的生命力的表现。

例如同样是打开文件的接口,底层实现完全不同,但是通过完全一样的接口,不同的路径以及Mount机制,实现了同时支持。其他还有Procfs, pipe等。

int open(const char *path, int oflag, .../*,mode_t mode */);

例如这里的cephfs和本地文件系统,底层对应完全不同的实现,但是上层client可以不用区分对待,采用同样的接口来操作,只通过路径不同来区分。

基于上面的这些原因,我们知道File API为什么能够如此成功。事实上,它是如此的成功以至于今天的*-nix操作系统,everything is filed based.

尽管我们有了一个非常好的例子File API,但是__要设计一个能够长期保持稳定的API是一项及其困难的事情__,因此仅有一个好的参考还不够,下面再试图展开去讨论一些更细节的问题。

Document well 写详细的文档

写详细的文档,并保持更新。 关于这一点,其实无需赘述,现实是,很多API的设计和维护者不重视文档的工作。

在一个面向服务化/Micro-service化架构的今天,一个应用依赖大量的服务,而每个服务API又在不断的演进过程中,__准确的记录每个字段和每个方法,并且保持更新__,对于减少客户端的开发踩坑、减少出问题的几率,提升整体的研发效率至关重要。

Carefully define the "resource" of your API 仔细的定义“资源”

如果适合的话,选用“资源”加操作的方式来定义。今天很多的API都可以采用这样一个抽象的模式来定义,这种模式有很多好处,也适合于HTTP的RESTful API的设计。但是在设计API时,一个重要的前提是对Resource本身进行合理的定义。什么样的定义是合理的?Resource资源本身是对一套API操作核心对象的一个抽象Abstraction。

抽象的过程是__去除细节的过程__。在我们做设计时,如果现实世界的流程或者操作对象是具体化的,抽象的Object的选择可能不那么困难,但是对于哪些细节应该包括,是需要很多思考的。例如对于文件的API,可以看出,文件File这个Resource(资源)的抽象,是“可以由一个字符串唯一标识的数据记录”。这个定义去除了文件是如何标识的(这个问题留给了各个文件系统的具体实现),也去除了关于如何存储的组织结构(again,留给了存储系统)细节。

虽然我们希望API简单,但是更重要的是__选择对的实体来建模__。在底层系统设计中,我们倾向于更简单的抽象设计。有的系统里面,域模型本身的设计往往不会这么简单,需要更细致的考虑如何定义Resource。一般来说,域模型中的概念抽象,如果能和现实中的人们的体验接近,会有利于人们理解该模型。__选择对的实体来建模__往往是关键。结合域模型的设计,可以参考相关的文章,例如阿白老师的文章【2】。

Choose the right level of abstraction 选择合适的抽象层

与前面的一个问题密切相关的,是在定义对象时需要选择合适的Level of abstraction(抽象的层级)。不同概念之间往往相互关联。仍然以File API为例。在设计这样的API时,选择抽象的层级的可能的选项有多个,例如:

  • 文本、图像混合对象
  • “数据块” 抽象
  • ”文件“抽象

这些不同的层级的抽象方式,可能描述的是同一个东西,但是在概念上是不同层面的选择。当设计一个API用于与数据访问的客户端交互时,“文件File“是更合适的抽象,而设计一个API用于文件系统内部或者设备驱动时,数据块或者数据块设备可能是合适的抽象,当设计一个文档编辑工具时,可能会用到“文本图像混合对象”这样的文件抽象层级。

又例如,数据库相关的API定义,底层的抽象可能针对的是数据的存储结构,中间是数据库逻辑层需要定义数据交互的各种对象和协议,而在展示(View layer)的时候需要的抽象又有不同【3】。

Prefer using different model for different layers 不同层建议采用不同的数据模型

这一条与前一条密切关联,但是强调的是不同层之间模型不同。

在服务化的架构下,数据对象在处理的过程中往往经历多层,例如上面的View-Logic model-Storage是典型的分层结构。在这里我们的建议是不同的Layer采用不同的数据结构。John Ousterhout 【8】书里面则更直接强调:Different layer, different abstraction。

例如网络系统的7层模型,每一层有自己的协议和抽象,是个典型的例子。而前面的文件API,则是一个Logic layer的模型,而不同的文件存储实现(文件系统实现),则采用各自独立的模型(如快设备、内存文件系统、磁盘文件系统等各自有自己的存储实现API)。

当API设计倾向于不同的层采用一样的模型的时候(例如一个系统使用后段存储服务与自身提供的模型之间,见下图),可能意味着这个Service本身的职责没有定义清楚,是否功能其实应该下沉?

不同的层采用同样的数据结构带来的问题还在于API的演进和维护过程。一个系统演进过程中可能需要替换掉后端的存储,可能因为性能优化的关系需要分离缓存等需求,这时会发现将两个层的数据绑定一起(甚至有时候直接把前端的json存储在后端),会带来不必要的耦合而阻碍演进。

Naming and identification of the resource 命名与标识

当API定义了一个资源对象,下面一般需要的是提供命名/标识(Naming and identification)。在naming/ID方面,一般有两个选择(不是指系统内部的ID,而是会暴露给用户的):

  • 用free-form string作为ID(string nameAsId)
  • 用结构化数据表达naming/ID

何时选择哪个方法,需要具体分析。采用Free-form string的方式定义的命名,为系统的具体实现留下了最大的自由度。带来的问题是命名的内在结构(如路径)本身并非API强制定义的一部分,转为变成实现细节。如果命名本身存在结构,客户端需要有提取结构信息的逻辑。这是一个需要做的平衡。

例如文件API采用了free-form string作为文件名的标识方式,而文件的URL则是文件系统具体实现规定。这样,就容许Windows操作系统采用"D:\Documents\File.jpg"而Linux采用"/etc/init.d/file.conf"这样的结构了。而如果文件命名的数据结构定义为

{disk: string,path: string
}

这样结构化的方式,透出了"disk""path"两个部分的结构化数据,那么这样的结构可能适应于Windows的文件组织方式,而不适应于其他文件系统,也就是说泄漏了实现细节。

如果资源Resource对象的抽象模型自然包含结构化的标识信息,则采用结构化方式会简化客户端与之交互的逻辑,强化概念模型。这时牺牲掉标识的灵活度,换取其他方面的优势。例如,银行的转账账号设计,可以表达为

{account: numberrouting: number
}

这样一个结构化标识,由账号和银行间标识两部分组成,这样的设计含有一定的业务逻辑在内,但是这部分业务逻辑是__被描述的系统内在逻辑而非实现细节__,并且这样的设计可能有助于具体实现的简化以及避免一些非结构化的字符串标识带来的安全性问题等。因此在这里结构化的标识可能更适合。

另一个相关的问题是,__何时应该提供一个数字unique ID?__ 这是一个经常遇到的问题。有几个问题与之相关需要考虑:

  • 是否已经有结构化或者字符串的标识可以唯一、稳定标识对象?如果已经有了,那么就不一定需要numerical ID;
  • 64位整数范围够用吗?
  • 数字ID可能不是那么用户友好,对于用户来讲数字的ID会有帮助吗?

如果这些问题都有答案而且不是什么阻碍,那么使用数字ID是可以的,__否则要慎用数字ID__。

Conceptually what are the meaningful operations on this resource? 对于该对象来说,什么操作概念上是合理的?

在确定下来了资源/对象以后,我们还需要定义哪些操作需要支持。这时,考虑的重点是“__概念上合理(Conceptually reasonable)__”。换句话说,operation + resource 连在一起听起来自然而然合理(如果Resource本身命名也比较准确的话。当然这个“如果命名准确”是个big if,非常不容易做到)。操作并不总是CRUD(create, read, update, delete)。

例如,一个API的操作对象是额度(Quota),那么下面的操作听上去就比较自然:

  • Update quota(更新额度),transfer quota(原子化的转移额度)

但是如果试图Create Quota,听上去就不那么自然,因额度这样一个概念似乎表达了一个数量,概念上不需要创建。额外需要思考一下,这个对象是否真的需要创建?我们真正需要做的是什么?

For update operations, prefer idempotency whenever feasible 更新操作,尽量保持幂等性

Idempotency幂等性,指的是一种操作具备的性质,具有这种性质的操作可以被多次实施并且不会影响到初次实施的结果“the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.”【3】

很明显Idempotency在系统设计中会带来很多便利性,例如客户端可以更安全的重试,从而让复杂的流程实现更为简单。但是Idempotency实现并不总是很容易。

  • Create类型的idempotency
    创建的Idempotency,多次调用容易出现重复创建,为实现幂等性,常见的做法是使用一个__client-side generated de-deduplication token(客户端生成的唯一ID)__,在反复重试时使用同一个Unique ID,便于服务端识别重复。
  • Update类型的Idempotency
    更新值(update)类型的API,应该避免采用"Delta"语义,以便于实现幂等性。对于更新类的操作,我们再简化为两类实现方式

    • Incremental(数量增减),如IncrementBy(3)这样的语义
    • SetNewTotal(设置新的总量)

    IncrementBy 这样的语义重试的时候难以避免出错,而SetNewTotal(3)(总量设置为x)语义则比较容易具备幂等性。
    当然在这个例子里面,也需要看到,IncrementBy也有有点,即多个客户请求同时增加的时候,比较容易并行处理,而SetTotal可能导致并行的更新相互覆盖(或者相互阻塞)。
    这里,可以认为更新增量设置新的总量这两种语义是不同的优缺点,需要根据场景来解决。如果必须优先考虑并发更新的情景,可以使用更新增量的语义,并辅助以Deduplication token解决幂等性。

  • __Delete类型idempotency__:Delete的幂等性问题,往往在于一个对象被删除后,再次试图删除可能会由于数据无法被发现导致出错。这个行为一般来说也没什么问题,虽然严格意义上不幂等,但是也无副作用。如果需要实现Idempotency,系统也采用了Archive->Purge生命周期的方式分步删除,或者持久化Purge log的方式,都能支持幂等删除的实现。

Compatibility 兼容

API的变更需要兼容,兼容,兼容!重要的事情说三遍。这里的兼容指的是向后兼容,而兼容的定义是不会Break客户端的使用,也即__老的客户端能否正常访问服务端的新版本(如果是同一个大版本下)不会有错误的行为__。这一点对于远程的API(HTTP/RPC)尤其重要。关于兼容性,已经有很好的总结,例如【4】提供的一些建议。

常见的__不兼容__变化包括(但不限于)

  • 删除一个方法、字段或者enum的数值
  • 方法、字段改名
  • 方法名称字段不改,但是语义和行为的变化,也是不兼容的。这类比较容易被忽视。

    更具体描述可以参加【4】。
    

另一个关于兼容性的重要问题是,__如何做不兼容的API变更__?通常来说,不兼容变更需要通过一个__Deprecation process,在大版本发布时来分步骤实现__。关于Deprecation process,这里不展开描述,一般来说,需要保持过去版本的兼容性的前提下,支持新老字段/方法/语义,并给客户端足够的升级时间。这样的过程比较耗时,也正是因为如此,我们才需要如此重视API的设计。

有时,一个面向内部的API升级,往往开发的同学倾向于选择高效率,采用一种叫”同步发布“的模式来做不兼容变更,即通知已知的所有的客户端,自己的服务API要做一个不兼容变更,大家一起发布,同时更新,切换到新的接口。这样的方法是非常不可取的,原因有几个:

  • 我们经常并不知道所有使用API的客户
  • 发布过程需要时间,无法真正实现“同步更新”
  • 不考虑向后兼容性的模式,一旦新的API有问题需要回滚,则会非常麻烦,这样的计划八成也不会有回滚方案,而且客户端未必都能跟着回滚。

因此,对于在生产集群已经得到应用的API,强烈不建议采用“同步升级”的模式来处理不兼容API变更。

Batch mutations 批量更新

批量更新如何设计是另一个常见的API设计决策。这里我们常见有两种模式:

  • 客户端批量更新,或者
  • 服务端实现批量更新。

    如下图所示。
    

API的设计者可能会希望实现一个服务端的批量更新能力,但是我们建议要尽量避免这样做。__除非对于客户来说提供原子化+事务性的批量很有意义(all-or-nothing)__,否则实现服务端的批量更新有诸多的弊端,而客户端批量更新则有优势:

  • 服务端批量更新带来了API语义和实现上的复杂度。例如当部分更新成功时的语义、状态表达等
  • 即使我们希望支持批量事物,也要考虑到是否不同的后端实现都能支持事务性
  • 批量更新往往给服务端性能带来很大挑战,也容易被客户端滥用接口
  • 在客户端实现批量,可以更好的将负载由不同的服务端来承担(见图)
  • 客户端批量可以更灵活的由客户端决定失败重试策略

Be aware of the risks in full replace 警惕全体替换更新模式的风险

所谓Full replacement更新,是指在Mutation API中,用一个全新的Object/Resource去替换老的Object/Resource的模式。API写出来大概是这样的

UpdateFoo(Foo newFoo);

这是非常常见的Mutation设计模式。但是这样的模式有一些潜在的风险作为API设计者必须了解。

使用Full replacement的时候,更新对象Foo在服务端可能已经有了新的成员,而客户端尚未更新并不知道该新成员。服务端增加一个新的成员一般来说是兼容的变更,但是,如果该成员之前被另一个知道这个成员的client设置了值,而这时一个不知道这个成员的client来做full-replace,该成员可能就会被覆盖。

更安全的更新方式是采用Update mask,也即在API设计中引入明确的参数指明哪些成员应该被更新。

UpdateFoo {Foo newFoo; boolen update_field1; // update maskboolen update_field2; // update mask
}

或者update mask可以用repeated "a.b.c.d“这样方式来表达。

不过由于这样的API方式维护和代码实现都复杂一些,采用这样模式的API并不多。所以,本节的标题是“be aware of the risk“,而不是要求一定要用update mask。

Don't create your own error codes or error mechanism 不要试图创建自己的错误码和返回错误机制

API的设计者有时很想创建自己的Error code,或者是表达返回错误的不同机制,因为每个API都有很多的细节的信息,设计者想表达出来并返回给用户,想着“用户可能会用到”。但是事实上,这么做经常只会使API变得更复杂更难用。

Error-handling是用户使用API非常重要的部分。为了让用户更容易的使用API,最佳的实践应该是用标准、统一的Error Code,而不是每个API自己去创立一套。例如HTTP有规范的error code 【7】,Google Could API设计时都采用统一的Error code等【5】。

为什么不建议自己创建Error code机制?

  • Error-handling是客户端的事,而对于客户端来说,是很难关注到那么多错误的细节的,一般来说最多分两三种情况处理。往往客户端最关心的是"这个error是否应该重试(retryable)"还是应该继续向上层返回错误,而不是试图区分不同的error细节。这时多样的错误代码机制只会让处理变得复杂
  • 有人觉得提供更多的自定义的error code有助于传递信息,但是这些信息除非有系统分别处理才有意义。如果只是传递信息的话,error message里面的字段可以达到同样的效果。

More

更多的Design patterns,可以参考[5] Google Cloud API guide,[6] Microsoft API design best practices等。不少这里提到的问题也在这些参考的文档里面有涉及,另外他们还讨论到了像versioning,pagination,filter等常见的设计规范方面考虑。这里不再重复。

参考文献

【1】File wiki https://en.wikipedia.org/wiki/Computer_file
【2】阿白,域模型设计系列文章,https://yq.aliyun.com/articles/6383
【3】Idempotency, wiki https://en.wikipedia.org/wiki/Idempotence
【4】Compatibility https://cloud.google.com/apis/design/compatibility
【5】API Design patterns for Google Cloud, https://cloud.google.com/apis/design/design_patterns
【6】API design best practices, Microsoft https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design
【7】Http status code https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
【8】A philosophy of software design, John Ousterhout

 


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/520122.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2018年深度学习的主要进步

在过去几年中,深度学习改变了整个人工智能的发展。深度学习技术已经开始在医疗保健,金融,人力资源,零售,地震检测和自动驾驶汽车等领域的应用程序中出现。至于现有的成果表现也一直在稳步提高。 在学术层面&#xff0…

大横琴科技联合阿里云发布全国首个跨境服务创新平台琴澳通

12月3日,阿里云广东峰会期间,大横琴科技公司联合阿里云发布了全国首个跨境服务创新平台。基于该平台,全国首个跨境服务APP“琴澳通”也正式发布。“琴澳通”将为澳门企业及个人提供服务,推动澳门和广东两地的产业经济联动&#xf…

各类监督方法流行趋势分析

又到一年的年末了,到了进行总结并展望来年的时候了,在这里预祝各位新的一年顺利。闲话少叙,本文将对有监督学习方法进行总结。 机器学习领域在过去几十年中经历了巨大的变化,不可否认的是,虽然有些方法已经存在了很长…

2018年度机器学习50大热门网文

新的一年新气象,总结过去一年,展望新的一年。站在巨人的肩膀上前行,肯定会事半功倍。因此,本文从2018年1月至12月期间挑选出近22,000篇机器学习文章,并进行比较,以挑选出能够提升2019年数据科学技能的前50名…

广东制造按下加速键  千家中小企业上阿里云工业互联网

阿里云广东峰会上,阿里巴巴副总裁、阿里云智能IoT事业部总经理库伟表示,过去2年阿里云与合作伙伴一起助力数千家广东中小企业的数字化转型综合成本降低了70%。 都说中国制造看广东,云计算、物联网、人工智能已成为广东制造发展新引擎。“我们…

阿里云图数据库GraphDB上线,助力图数据处理

GraphDB简介 GraphDB图数据库适用于存储,管理,查询复杂并且高度连接的数据,图库的结构特别适合发现大数据集下数据之间的共性和特性,特别善于释放蕴含在数据关系之间的巨大价值。GraphDB引擎本身并不额外收费,仅收取云…

pom 导入mysql连接,maven项目中的jdbc连接步骤

在maven项目pom.xml中到入驱动包(以下是驱动包代码)mysqlmysql-connector-java8.0.15导入成功会看到Maven Dependencies下有个mysql-connector-Java包然后按下面步骤:package com.base.day16;import java.sql.Connection;import java.sql.DriverManager;import java…

一场稳定、高清、流畅的大型活动直播是怎么炼成的?

双11猫晚是家喻户晓的综艺晚会,在今年的双11,阿里集团为2500万用户提供了一场在线直播视觉盛宴。网友评价这是一场既稳定流畅又高清的直播,当然在这背后离不开阿里云的技术支持。 本次天猫晚会中,视频云首次采用4k和50帧的技术&a…

阿里云联合顶级卫星影像公司发布数字地球引擎

12月3日,在阿里云广东峰会上,阿里云联合超图软件、长光卫星、Maxar技术、四维测绘等顶级卫星影像产业链公司发布数字地球引擎,提供开放式的影像数据集、遥感AI能力、丰富的API接口等,在国土资源监管、水利河道治理、自然环境保护和…

PLSQL 快捷键

文章目录1.创建文本文件2. 添加自定义快捷键3. 配置1.创建文本文件 在D:\software\PLSQL Developer\PlugIns目录下面创建shortcuts.txt 2. 添加自定义快捷键 iINSERT uUPDATE sSELECT fFROM wWHERE oORDER BY dDELETE dfDELETE FROM sfSELECT * FROM scSELECT COUNT(*) FROM…

python numpy sum函数,numpy.sum()的使用详解

numpy的sum函数可接受的参数是:sum(a, axisNone, dtypeNone, outNone, keepdimsnp._NoValue)在参数列表中:a是要进行加法运算的向量/数组/矩阵axis的值可以为None,也可以为整数和元组其形参的注释如下:a : array_like elements to sum.a:用于进行加法运算…

云栖专辑 | 阿里开发者们的第9个感悟:脱离产品价值,是难以出创造性的代码的

2015年12月20日,云栖社区上线。2018年12月20日,云栖社区3岁。 阿里巴巴常说“晴天修屋顶”。 在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备。 所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的…

提升沟通效率52%  阿里政务钉钉助力政府数字化转型

阿里云广东峰会期间,阿里巴巴政务钉钉事业部总经理叶军表示,在浙江省试点应用政务钉钉期间,政府人员沟通效率提高了52%。政务钉钉是在线政务协同平台,它整合了政务专属通讯录、千人千面的工作台、智能会议、移动办公等诸多功能&am…

十余位权威专家深度解读,达摩院2019十大科技趋势点燃科技热情

2019年的第一个工作日,阿里巴巴达摩院重磅发布了2019十大科技趋势,引发社会各界对未来科技的讨论和向往。这一发布同样引来科学界的普遍关注。来自包括中科院、清华大学、佛罗里达大学、杜克大学等权威学术机构的十余位专家就此发表评论,深度…

云栖专辑 | 阿里开发者们的第10个感悟:产品经理最优秀的能力,是框架思维,脑海中有蓝图

2015年12月20日,云栖社区上线。2018年12月20日,云栖社区3岁。 阿里巴巴常说“晴天修屋顶”。 在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备。 所以社区特别制作了这个专辑——分享给开发者们20个弥足珍贵的…

Dubbo作者亲述:那些辉煌、沉寂与重生的故事

摘要: Dubbo 这个名字,最后会变成一个 Apache 的商标,会成为一个在 GitHub 上有 2 万多人关注、一百多人参与贡献的超级项目。 梁飞在 2011 年开源 Dubbo 这个项目的时候,完全没有想过,Dubbo 这个名字,最后…

阿里云提出“云+Fintech”新金融战略 已助上万家金融机构上云

“云是不可逆转的大战略。”12月3日,阿里云智能新金融事业部总经理刘伟光在2019阿里云广东峰会上表示,“云Fintech”将成为数字金融时代的新基础设施,阿里云已整合蚂蚁金服金融科技开放业务能力,利用全栈式金融级技术方案助力金融…