摆脱“空”检查的盛宴:使用JSON Patch正确执行PATCH

今天,我们将就REST(ful)服务和API进行一次对话,更准确地说,围绕许多经验丰富的开发人员正在努力解决的一个独特主题。 为了使事情更直观,我们将讨论Web API,其中REST(ful)原则遵循HTTP协议并大量利用HTTP方法的语义,(通常但不一定)使用JSON表示状态。

一种特殊的HTTP方法非常引人注目,尽管其含义听起来很简单,但实现方法远非如此。 是的,我们正在寻找您, PATCH 。 那到底是什么问题呢? 这只是更新,对不对? 是的,实质上,在基于HTTPREST(ful)Web服务的上下文中, PATCH方法的语义是资源的部分更新。 现在,Java开发人员将如何做到这一点? 这就是乐趣的开始。

让我们来看一个非常简单的图书管理API示例,该示例使用最新的JSR 370:RESTful Web服务的Java API(JAX-RS 2.1)规范(最终包括@PATCH注释!)和出色的Apache CXF框架进行建模 。 我们的资源只是一个非常简单的Book类。

public class Book {
    private String title;
    private Collection>String< authors;
    private String isbn;
}

您将如何使用PATCH方法实施部分更新? 可悲的是,强力解决方案,即null宴席,在这里显然是赢家。

@PATCH
@Path("/{isbn}")
@Consumes(MediaType.APPLICATION_JSON)
public void update(@PathParam("isbn") String isbn, Book book) {
    final Book existing = bookService.find(isbn).orElseThrow(NotFoundException::new);
        
    if (book.getTitle() != null) {
        existing.setTitle(book.getTitle());
    }

    if (book.getAuthors() != null) {
        existing.setAuthors(book.getAuthors());
    }
        
    // And here it goes on and on ...
    // ...
}

简而言之,这是空保护的PUT克隆。 可能有人会声称这行得通,并在这里宣布胜利。 但是希望对我们大多数人来说,这种方法显然存在很多缺陷,因此永远不应该采用。 备择方案? 是的,绝对是RFC-6902:JSON补丁 ,目前还不是官方标准,但已经实现了。

RFC-6902:JSON修补程序通过表达一系列操作以应用于JSON文档,从而彻底改变了游戏。 为了说明这一思想的实际效果,让我们从更改书名的简单示例入手,以所需的结果来描述。

{ "op": "replace", "path": "/title", "value": "..." }

看起来很干净,那么添加作者呢? 简单 …

{ "op": "add", "path": "/authors", "value": ["...", "..."] }

太棒了,卖完了,但是……从实现角度看,这似乎需要很多工作,不是吗? 如果我们依赖最新和最强大的JSR 374:JSON处理1.1的Java API,它完全支持RFC-6902:JSON Patch,则不是真的。 有了合适的工具,这次让我们做对了。

org.glassfish
    javax.json
    1.1.2

有趣的是,没有多少人知道Apache CXF和通常的任何JAX-RS兼容框架都与JSON-P紧密集成并支持其基本数据类型。 对于Apache CXF ,只需添加cxf-rt-rs-extension-providers模块依赖项即可:

org.apache.cxf
    cxf-rt-rs-extension-providers
    3.2.2

并在服务器工厂bean中注册JsrJsonpProvider ,例如:

@Configuration
public class AppConfig {
    @Bean
    public Server rsServer(Bus bus, BookRestService service) {
        JAXRSServerFactoryBean endpoint = new JAXRSServerFactoryBean();
        endpoint.setBus(bus);
        endpoint.setAddress("/");
        endpoint.setServiceBean(service);
        endpoint.setProvider(new JsrJsonpProvider());
        return endpoint.create();
    }
}

将所有部分连接在一起,我们的PATCH操作可以使用JSR 374:用于JSON Processing 1.1的Java API来实现,仅需几行:

@Service
@Path("/catalog")
public class BookRestService {
    @Inject private BookService bookService;
    @Inject private BookConverter converter;

    @PATCH
    @Path("/{isbn}")
    @Consumes(MediaType.APPLICATION_JSON)
    public void apply(@PathParam("isbn") String isbn, JsonArray operations) {
        final Book book = bookService.find(isbn).orElseThrow(NotFoundException::new);
        final JsonPatch patch = Json.createPatch(operations);
        final JsonObject result = patch.apply(converter.toJson(book));
        bookService.update(isbn, converter.fromJson(result));
    }
}

BookConverter执行Book类及其JSON表示之间的转换(反之亦然),我们正在手工进行操作,以说明JSR 374:JSON处理1.1的Java API提供的另一种功能。

@Component
public class BookConverter {
    public Book fromJson(JsonObject json) {
        final Book book = new Book();
        book.setTitle(json.getString("title"));
        book.setIsbn(json.getString("isbn"));
        book.setAuthors(
            json
                .getJsonArray("authors")
                .stream()
                .map(value -> (JsonString)value)
                .map(JsonString::getString)
                .collect(Collectors.toList()));
        return book;
    }

    public JsonObject toJson(Book book) {
        return Json
            .createObjectBuilder()
            .add("title", book.getTitle())
            .add("isbn", book.getIsbn())
            .add("authors", Json.createArrayBuilder(book.getAuthors()))
            .build();
    }
}

最后,让我们将这个简单的JAX-RS 2.1 Web API包装到漂亮的Spring Boot信封中。

@SpringBootApplication
public class BookServerStarter {    
    public static void main(String[] args) {
        SpringApplication.run(BookServerStarter.class, args);
    }
}

并运行它。

mvn spring-boot:run

结束讨论,让我们通过在目录中故意添加一本不完整的书 ,来探讨一些更实际的例子。

$ curl -i -X POST http://localhost:19091/services/catalog -H "Content-Type: application\json" -d '{
       "title": "Microservice Architecture",
       "isbn": "978-1491956250",
       "authors": [
           "Ronnie Mitra",
           "Matt McLarty"
       ]
   }'

HTTP/1.1 201 Created
Date: Tue, 20 Feb 2018 02:30:18 GMT
Location: http://localhost:19091/services/catalog/978-1491956250
Content-Length: 0

在本书的描述中,我们要纠正一些错误,即将标题设置为完整的“微服务体系结构:原则,实践和文化的 一致性 ,并包括失踪的合著者Irakli NadareishviliMike Amundsen 。 借助我们不久前开发的API,这很容易。

$ curl -i -X PATCH http://localhost:19091/services/catalog/978-1491956250 -H "Content-Type: application\json" -d '[
       { "op": "add", "path": "/authors/0", "value": "Irakli Nadareishvili" },
       { "op": "add", "path": "/authors/-", "value": "Mike Amundsen" },
       { "op": "replace", "path": "/title", "value": "Microservice Architecture: Aligning Principles, Practices, and Culture" }
   ]'

HTTP/1.1 204 No Content
Date: Tue, 20 Feb 2018 02:38:48 GMT

前两个操作的路径引用可能看起来有些混乱,但是不用担心,让我们澄清一下。 因为authors是一个集合(或就JSON数据类型而言,是一个数组),所以我们可以使用RFC-6902:JSON Patch数组索引符号来指定要插入新元素的确切位置。 第一个操作使用索引'0'来表示头部位置,而第二个操作使用'-'占位符来简化说“添加到集合的末尾”。 如果我们在更新后立即检索到该书,则应该看到我们所做的修改完全按照我们的要求进行了应用。

$ curl http://localhost:19091/services/catalog/978-1491956250

{
    "title": "Microservice Architecture: Aligning Principles, Practices, and Culture",
    "isbn": "978-1491956250",
    "authors": [
        "Irakli Nadareishvili",
        "Ronnie Mitra",
        "Matt McLarty",
        "Mike Amundsen"
    ]
}

干净,简单而强大。 公平地说,要付出代价是一种额外的JSON操作形式(以应用补丁程序),但是值得付出努力吗? 我相信是……

下次您要设计新颖的REST(ful)Web API时 ,请认真考虑RFC-6902:JSON补丁以支持资源的PATCH实现。 我相信还将与JAX-RS进行更紧密的集成(如果还没有的话),以直接支持JSONPatch类及其家族。

最后但并非最不重要的一点是,在本文中,我们仅涉及服务器端实现,但JSR 374:用于JSON处理1.1的Java API也包括方便的客户端支架,从而对补丁提供了完整的编程控制。

final JsonPatch patch = Json.createPatchBuilder()
    .add("/authors/0", "Irakli Nadareishvili")
    .add("/authors/-", "Mike Amundsen")
    .replace("/title", "Microservice Architecture: Aligning Principles, Practices, and Culture")
    .build();

完整的项目资源可在Github上找到

翻译自: https://www.javacodegeeks.com/2018/02/run-away-null-checks-feast-patch-properly-json-patch.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值