使用HAL设计RESTful API - 序列化模型关系

时间:2013-04-12 23:59:42

标签: json rest serialization hateoas hal-json

我对REST比较陌生,但我一直在做关于RESTful应该如何做的功课。现在我正在尝试为我的模型创建一个实现JSON + HAL序列化程序的RESTful api,它与其他模型有关系。
python中的示例模型:

class Category(Model):
    name = CharField()
    parent = ManyToOneField(Category)
    categories = OneToManyField(Category)
    products = ManyToManyField(Product)

class Product(Model):
    name = CharField()
    price = DecimalField()
    related = ManyToManyField(Product)
    categories = ManyToManyField(Category)

假设我们有一个类别“目录”,其子类别为“食物”,产品“汉堡”和“热狗”都是相关的。
第一个问题。类别和产品应该是资源,所以他们需要一个URI,我应该在我的模型中实现一个uri字段并将其存储在数据库中,或者在运行时以某种方式计算它,多个标识符(URI)呢? 第二个问题。 可发现性,在Hal格式中应该“GET /”和不同节点返回的内容使api容易被自己发现。

{
  "_links":{
    "self":{
      "href":"/"
    },
    "categories":[
      {
        "href":"/catalog"
      }
    ]
  }
}

第三个问题。添加为属性,嵌入或链接。示例“GET / catalog / food”:

{
  "_links":{
    "self":{
      "href":"/catalog/food"
    }
  },
  "name":"food",
  "parent":"/catalog",
  "categories":[],
  "products":[
    "/products/burger",
    "/products/hot-dog"
  ]
}

{
  "_links":{
    "self":{
      "href":"/catalog/food"
    },
    "parent":{
      "href":"/catalog"
    },
    "categories":[

    ],
    "products":[
      {
        "href":"/products/burger"
      },
      {
        "href":"/products/hot-dog"
      }
    ]
  },
  "name":"food"
}

{
  "_links":{
    "self":{
      "href":"/catalog/food"
    }
  },
  "name":"food",
  "_embedded":{
    "parent":{
      "_links":{
        "self":{
          "href":"/catalog"
        }
      },
      "name":"catalog",
      ...
    },
    "categories":[

    ],
    "products":[
      {
        "_links":{
          "self":{
            "href":"/products/burger"
          }
        },
        "name":"burger",
        ...
      },
      {
        "_links":{
          "self":{
            "href":"/products/hot-dog"
          }
        },
        "name":"hot-dog",
        ...
      }
    ]
  }
}

第四个问题。返回结构时我应该有多深。示例“GET / catalog

{
  "_links":{
    "self":{
      "href":"/catalog"
    }
  },
  "name":"catalog",
  "parent":null,
  "categories":[
    {
      "name":"food",
      "parent":{...},
      "categories":[],
      "products":[
        {
          "name":"burger",
          "price":"",
          "categories":[...],
          "related":[...]
        },
        {
          "name":"hot-dog",
          "price":"",
          "categories":[...],
          "related":[...]
        }
      ]
    }
  ],
  "products": []
}

2 个答案:

答案 0 :(得分:6)

关于第一个问题:我不会将URI存储在数据库中。您可以在运行时轻松地在控制器内计算它们,并且控制器负责关注URI。通过这种方式,您可以保持模型和API的分离,如果您决定在将来更改API结构,则无需使用新URI更新整个数据库。

关于多个标识符,我不确定问题是什么,但在我看来,它与DB无关,它是路由器和控制器应该关心如何处理任何URI。

关于第二个问题:首先,作为旁注:我会将 categories 这个词作为URI的一部分。例如,我有http://domain.com/api/categories/catalog/food。这样,您就可以使API更具描述性并且更具“黑客性”,这意味着用户应该能够移除/catalog/food部分并期望收到包含所有可用类别的集合。

现在,关于GET应返回以允许发现的内容:我认为已经从您的URI结构中明确了这一点。当用户点击GET /categories时,他希望得到一个包含类别的列表(每个类别的名称和URI,以保持轻量级),当他跟随其中一个URI(如GET /categories/catalog)时,他应该收到资源catalog这是一个类别。同样,当他想要GET /products/burger时,他应该会收到一个包含模型中所有属性的产品资源。您可能需要查看this example有关回复结构的信息。

关于第3个问题:同样,the same example可以帮助您构建结构。我认为您的第二种回应方式更接近于此,但我还会添加name字段,而不仅仅是href

关于第4个问题:当GET请求期望收集资源(例如GET /categories)时,我建议只为每个资源提供必要的资源,即名称和每个URI的URI,只有当用户遵循所需的URI时,他才能收到其他信息。

在您的示例中,catalog是一个资源,因此GET /categories/catalog我当然会包含资源(目录)的name及其自身链接,以及{{1}与它相关的{}},parentsub-categories,我只会提供每个的名称和URI,以保持清晰。 但是:这是关于设计API的一般想法。在您的实际问题中,应根据您的具体业务问题做出决定。我的意思是,如果您的API是关于带有类别和菜肴的餐馆菜单,您可能想要包括价格或小描述,即使不是响应实际产品而是回复产品系列,因为可能对您的用户而言,一个重要的信息。因此,一般情况下,在回复资源列表时提供所有必要的信息(您只知道这些问题是什么),并在回复特定资源时提供资源的所有详细信息。

答案 1 :(得分:1)

  1. 我会在数据库中存储一些内容并在运行时计算URI。这样,如果你移动盒子,它不是静态的。
  2. 创建“书签”页面。我们创建的页面只是一个包含其rels的链接列表。我相信HAL具体定义了这一点。书签页面是其他页面需要了解的唯一页面
  3. 不确定这个
  4. 你走多远取决于你。现在在我的工作场所有一个很大的争论,就是细粒与粗粒。我将使用小资源做精细的粒度以保持api简单,但随后使用Expand-ability概念。它是Subbu的REST书第35页定义的复合资源概念与Netflix使用的扩展概念的结合。 http://developer.netflix.com/docs/REST_API_Conventions