Link to home
Start Free TrialLog in
Avatar of A
A

asked on

Update resource and nested/related resources in RESTFul arquitecture using PUT

I want to know what is the best approach to perform an Update using PUT in a resource with related or nested resources using a Restful design.

For example, I have the next example:

ResourceClasses
public class Invoice
{
    public int Id { get; set; }

    public Customer Customer { get; set; }

    public InvoiceDetail[] InvoiceDetailCollection { get; set; }
}

public class Customer
{
    public int Id { get; set; }

    public string FullName { get; set; }

    public string IdentificationNumber { get; set; }
}

public class InvoiceDetail
{
    public int Id { get; set; }

    public int Quantity { get; set; }

    public decimal TotalPrice { get; set; }

    public Product Product { get; set; }
}

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; }

    public decimal UnitPrice { get; set; }
}

Open in new window

So we have an Invoice with a related Customer resource and an array of Details, each Detail has a related Product resource.

In that case, if I need to update the Invoice with id=1, I perform a PUT request to the endpoint /invoices/1 with the resource in the body.

In the action, I perform the update of the changed values. Now, I can imagine two different situations:

1- Need to change the Invoice´s Customer. 2- Need to add/update/delete Invoice´s details.

Case 1: For this case, the solution is easy, I can check if the Id of the client is different and perform the update.

Case 2: In this case, the Client Application can send or not the Invoice´s Detail array. In the action, I can check if the Detail array is null or not, and if not, perfom the updates. The problem here, is that the array can be null because every details was deleted.

What would be the best way to handle this situation considering that it´s restful and not rest?

I thinked about adding a parameter in the PUT endpoint to indicate explicitly which nested resources it has to update.

For example:

[code]PUT /invoices/1?update=Client,InvoiceDetailCollection

Open in new window

[/code]
Then I can use a mapper which I already have, to map the nested/related resources´s names to the Invoice´s properties to know which resource I must be updated.

A quick example would be something like this:
if (update.Includes("InvoiceDetailCollection"))
{
    Update(invoice.DetailCollection);
}

Open in new window

I think this can be a nice solution, especially when my GET endpoints have a parameter named include where I can include all the related resources I want to return to the client:

For example:
GET /invoices/1
{  
   "id":1
}

Open in new window

GET /invoices/1?include=Customer
{  
   "id":1,
   "customer":{  
      "id":1,
      "fullName":"John Doe",
      "identificationNumber":"1451122"
   }
}

Open in new window


GET /invoices/1?include=Customer,DetailCollection[Product]
{  
   "id":1,
   "customer":{  
      "id":1,
      "fullName":"John Doe",
      "identificationNumber":"1451122"
   },
   "detailCollection":[  
      {  
         "id":1,
         "quantity":1,
         "totalPrice":10.50,
         "Product":{  
            "id":1,
            "name":"Coke",
            "unitPrice":10.50
         }
      }
   ]
}

Open in new window

Is the PUT (update) approach right? Is there any disadvantage of this solution? Any alternative? What am I missing?
Avatar of ste5an
ste5an
Flag of Germany image

You can of course do it like that. Cause in REST and restful services the URI describes one resource. Thus any modification should only be done to this resource. And what forms this resource is your job and you simply do it by defining it.

The only thing I see and this maybe also your problem is: Your looking at the invoice from the wrong perspective.

The first thing I thought about is naming, I'm not an English native speaker, but are the "details" not positions or line items? Sometimes finding the correct name is essential. This also applies to your Product element. While a single invoice line stems from a product, it is none. Product would indicate this is data from a products table, which is in many cases problematic (the price changes). Consider discounts applied to single items. Thus you need copies of those values. I would expect a structure like:

"lineItems":[
      {
         "id":1,
         "quantity":1,
         "totalPrice":10.50,
         "name":"Coke",
         "unitPrice":10.50,
         "discount":0.0,
         "productID":1
      }
   ]

Open in new window

Where productID points to the product in the product table, but the rest is a copy of that data.

The second thing is, when I request an invoice (GET /invoices/1), then I expect to get the entire invoice. Containing all the data a invoice needs. The idea using different formats by using a query parameter in the URI is ok  - in clean REST this should be a header, but this is a philosophical view, see also Richardson Maturity Model - cause you can have different representations of a resource. But these parameters should limit the content (GET /invoices/1?format=totalsOnly).
Avatar of A
A

ASKER

Thanks for your comment @ste5an.

This was only a quick example to ask the question.

The real question here is how to update a resource, but also updating/adding/deleting child resources. For example:

A client APP wants to modify an invoice and that invoice has severals items. If it just sends a PUT request with the Invoice, the Items would not be included.
Then, I have to "tell" the API that the Items (Details) must be included in the Update.

This is not a Rest, but RESTful.
I understood the part about the details. Consider this resource returned by your request:

GET /invoices/1
{
    "id":1,
    "customer":{
        "id":1,
        "fullName":"John Doe",
        "identificationNumber":"1451122"
    },
    "lineItems":[
        {
            "id":1,
            "quantity":1,
            "totalPrice":10.50,
            "name":"Coke",
            "unitPrice":10.50,
            "discount":0.0,
            "productID":1
        }
    ],
    "totalAmount":10.50,
    "exludedVat":2.1,
    "vatPercentage":20
}

Open in new window


Then a PUT has to update the line items, when they have changed. You can also change the customer.id, but - i hope - obviously not customer.fullName and customer.identificationNumber. This must be clearly done over its own resource available by GET /customer/1.

Now when you have a shortend version of it like

GET /invoices/1?format=totalsOnly
{
    "id":1,
    "totalAmount":10.50,
    "exludedVat":2.1,
    "vatPercentage":20
}

Open in new window


You need only to update the percentage and recalculate the total amount and vat amount.

The "problem" is simply how you define what your resource is. A invoce is, when not reduced, the entire data set needed to print a valid invoice on paper.
Avatar of A

ASKER

Yes I understand your point about related resources like Customer. The PUT can just switch the selected Customer, not change the Customer information.

But when it comes to the case of the lineItems, it is not clear.
LineItem is another resource. Invoice has a list of them.

Maybe the Client just wants to modify the Invoice header information and not the lineItems. Or maybe it wants to modify all (including deleting and adding).

Because of that, thats why I was thinking about incluiding which related entities the Client wants to update.
Because of that, thats why I was thinking about incluiding which related entities the Client wants to update.

Well, I think it should be clear. An invoice consists naturally of line items, they are the integral part of an invoice. It's the common defintion. Thus changing them by using the invoice resource is imho a must.
Thus also the different parameter usage of mine with format to reduce the dataset returned.

To simplify the usage you can,  of course, add an additional resource and endpoint like

GET /invoicelineitem/1
{
    "invoiceId":1,
    "id":1,
    "quantity":1,
    "totalPrice":10.50,
    "name":"Coke",
    "unitPrice":10.50,
    "discount":0.0,
    "productID":1
}

Open in new window


for single line item updates.
Avatar of A

ASKER

I know, but maybe, the client just wants to change for example, the date of the invoice and the customerId, but not the lineItems.
With a restful design, you perform a GET invoices/1. With that, I would be getting only the invoice´s info. For ex:

{  
   "id":1,
   "date":"2018-01-26",
   "customer":{  
      "id":1,
      "href":"customers/1"
   },
   "lineItemCollection":{  
      "href":"invoices/1/lineitems"
   }
}

Open in new window


If the clients performs a PUT with this json, it does not includes the details. In this case, the API would remove all the lineItems.
If the clients performs a PUT including the lineItems, the API would update/add/remove all the lineItems.
I have to admit, that it's pretty hard to explain that. Maybe it's to basic. It's simply about design decisions.

Your first decision is about how you define your resource. Thus the content. No format, no functionality besides retrieving it:

An invoice consists naturally of header information like an address, invoice date, reference number. It may also contain an address. Then it has line items. After that it may has further information paragraphs. That is what a invoice looks like.

REST/restful services should have meaningful semantics. Thus a GET /invoice/1 should return all the data necessary to e.g. print a complete invoice in one call. No filter (query parameter or header should be needed to get this information). Cause a URI identifies the resource and a parameter (or header) to filter/reshape the data returned is not part of the resource identifier. GET /invoice/1 and GET /invoice/1?format=totalsOnly or GET /invoice/1?include=Customer point all to the same resource named invoice.


3.4.  Query
   The query component contains non-hierarchical data that, along with
   data in the path component (Section 3.3), serves to identify a
   resource within the scope of the URI's scheme and naming authority
   (if any). [..;  RFC 3986]
While someone can argue about it, your resource GET /invoice/1 would return incomplete data, so that it is not sufficient to reproduce a real invoice. Which is bad semantics.

Your second decision is about functionality:

Do you want a single, closed endpoint? Then yes, a PUT /invoice/1 has also to change the line items. When the sender forgets to include the line items in a PUT request, thus when no "lineItems" array is present, then you can safely interpret this as "don't change them". But when the sender includes an empty array ("lineItems": []) or null ("lineItems": null), then you can also safely delete those items. Cause the intention is clear by the JSON semantics.

Obviously, you can add three endpoints GET /invoice/1 which is immutable, thus has no POST, PUT, PATCH, DELETE. And two mutable endpoints GET /invoiceHeadFoot/1 and GET /invoiceLineItemst/1.
This would be a clean separated approach.

I guess one of your problems is that you're trying to overload a single resource and thus getting weird semantics.

Your third decision is about format:

What format modifiers or reshaping of the result set do you want to offer? Using query parameters or headers (both is the best approach, but not mixed).

And as reminder: The first decision must be made independently of the others, Cause it is the fundamental decision. Don't think about functionality and format while defining your resource.

Choose what you think is better. Cause you're free to do so. But the goal of a clean architecture here is using clean, natural language with easy, natural semantics.

Choose my design ;)
Avatar of A

ASKER

I understand your point, but in that case I would have multiple endpoints and I would have to use multiple DTOs classes.
I thought of using a RESTFul architecture and HATEOAS. The main objective is to be able to re-use resources. My real design is the following one, lets say, an invoice (an example with data is at the end):

{
  "id": 0,
  "objectValue": {
    "customer": {
      "id": 0,
      "objectValue": {
        "name": "string",
        "location": {
          "id": 0,
          "objectValue": {
            "name": "string"
          },
          "links": [
            {
              "href": "string",
              "method": "string"
            }
          ],
          "className": "string"
        }
      },
      "links": [
        {
          "href": "string",
          "method": "string"
        }
      ],
      "className": "string"
    },
    "branchOffice": {
      "id": 0,
      "objectValue": {
        "name": "string",
        "location": {
          "id": 0,
          "objectValue": {
            "name": "string"
          },
          "links": [
            {
              "href": "string",
              "method": "string"
            }
          ],
          "className": "string"
        }
      },
      "links": [
        {
          "href": "string",
          "method": "string"
        }
      ],
      "className": "string"
    },
    "lineItemCollection": {
      "collection": [
        {
          "id": 0,
          "objectValue": {
            "rowNumber": 0,
            "product": {
              "id": 0,
              "objectValue": {
                "name": "string",
                "price": 0
              },
              "links": [
                {
                  "href": "string",
                  "method": "string"
                }
              ],
              "className": "string"
            },
            "quantity": 0
          },
          "links": [
            {
              "href": "string",
              "method": "string"
            }
          ],
          "className": "string"
        }
      ],
      "collectionPageInfo": {
        "previousHRef": "string",
        "nextHRef": "string",
        "pageNo": 0,
        "pageSize": 0,
        "pageCount": 0,
        "objectCount": 0
      },
      "links": [
        {
          "href": "string",
          "method": "string"
        }
      ],
      "className": "string"
    }
  },
  "links": [
    {
      "href": "string",
      "method": "string"
    }
  ],
  "className": "string"
}

Open in new window


Each resource is included in a container class which contains an id attribute and links to access it. Also, the complete object attributes are in the objectValue object. The objectValue object is only completed if the related resource or list of resources is in the include parameter of the GET request. Obviously, the main resource has their objectValue included by default.
With this, you can get the information that you actually need. Maybe the client just need the invoce with no customer information or lineItems:
GET invoices/1

But in the response, the client would know that it has a related customer with some id, and the link to it to access it.

If  the client want to include the Customer, it has to call invoices/1?include=Customer or perform a GET request to the Customer containar class GET link.

Then, I think this is highly reusable and performant.

Going back to modifying the invoice including or not lineItems, maybe I would select your solution of checking null or an empty array. Althought I consider that it can be a bit dangerous.

A full example with actual data (Some links may be missing):

GET invoices/2/?include=Customer,ItemListCollection

Open in new window


{
  "id": 2,
  "objectValue": {
    "customer": {
      "id": 2,
      "objectValue": {
        "name": "John Doe",
        "location": {
          "id": 2,
          ""links": [
        {
          "href": "api/location/2",
          "method": "GET"
        },
        {
          "href": "api/location",
          "method": "POST"
        }
      ],
          "className": "Location"
        }
      },
      "links": [
        {
          "href": "api/customers/1",
          "method": "GET"
        },
        {
          "href": "api/customers",
          "method": "POST"
        }
      ],
      "className": "Customer"
    },
    "branchOffice": {
      "id": 2,
      "links": [
        {
          "href": "api/branchOffice/2",
          "method": "GET"
        },
        {
          "href": "api/branchOffice",
          "method": "POST"
        }
      ],
      "className": "BranchOffice"
    },
    "itemListCollection": {
      "collection": [
        {
          "id": 1528,
          "objectValue": {
            "rowNumber": 3,
            "product": {
              "id": 1,
              "links": [
        {
          "href": "api/product/1",
          "method": "GET"
        },
        {
          "href": "api/product/1",
          "method": "POST"
        }
      ],
              "className": "Product"
            },
            "quantity": 2
          },
          "links": [
            {
              "href": "api/itemList/1528",
              "method": "GET"
            }
          ],
          "className": "ItemList"
        },
        {
          "id": 1530,
          "objectValue": {
            "rowNumber": 3,
            "product": {
              "id": 7,
              "links": [
        {
          "href": "api/product/7",
          "method": "GET"
        },
        {
          "href": "api/product/7",
          "method": "POST"
        }
              "className": "Product"
            },
            "quantity": 13
          },
          "links": [
            {
              "href": "api/itemList/1530",
              "method": "GET"
            }
          ],
          "className": "ItemList"
        },
        {
          "id": 1531,
          "objectValue": {
            "rowNumber": 3,
            "product": {
              "id": 2,
              "links": [
        {
          "href": "api/product/2",
          "method": "GET"
        },
        {
          "href": "api/product/2",
          "method": "POST"
        }
              "className": "Product"
            },
            "quantity": 12
          },
          "links": [
            {
              "href": "api/itemList/1531",
              "method": "GET"
            }
          ],
          "className": "Detail"
        }
      ],
      "links": [
        {
          "href": "api/master/2/itemLists",
          "method": "GET"
        }
      ],
      "className": "ItemList"
    }
  },
  "links": [
    {
      "href": "api/invoice/2",
      "method": "GET"
    },
    {
      "href": "api/invoice",
      "method": "POST"
    },
    {
      "href": "api/invoice/2",
      "method": "PUT"
    }
  ],
  "className": "Invoice"
}

Open in new window


I attached the example so you can understand my solution more clearly and maybe you can give me some advice or recommendation.

Thank you!
Don't take the following too serious, it's my opinon and maybe biased..

I understand your point, [..]
You don't.

[..]but in that case I would have multiple endpoints and I would have to use multiple DTOs classes.
 I thought of using a RESTFul architecture and HATEOAS. The main objective is to be able to re-use resources. My real design is the following one, lets say, an invoice (an example with data is at the end):
As I said you have to made some (independent) decisions. The order is important. When you define your resource named Invoice, DTO's aren't relevant. HATEOAS isn't relevant. Simply design the resource. Using simple, but correct and precise language. Ask the users, the specialists in the department how they define it. Use their wording (as long it is correct and simple). Don't use abbreviations.. The resource is your public interface. Thus you need to define this carefully, but semantically correct.

GET invoices/2?include=Customer,Details
This is not a naturally definition, when GET invoices/2 only returns some header data. This is bad semantics. This requires extra documentation, sample code and brain capacity of the consumer-side developer. It has a code small.

There are two principles in software design. The Law of Dementer and The Principle of least astonishment. Not getting a complete invoice by GET /invoice/1 violates their generalization. In the concrete case: when the consumer says invoice, he should get a complete invoice.

Each resource is included in a container
Nope! Each resources has at least one endpoint. HATEOAS only defines in its core the method how to transport the ability (possible methods) of your resource by using link ref to the methods on a resource. Thus using HATEOAS means something like

GET /invoices/1
{
    "id":1,
    "customer":{
        "id":1,
        "fullName":"John Doe",
        "identificationNumber":"1451122"
    },
    "lineItems":[
        {
            "id":123,
            "quantity":1,
            "totalPrice":10.50,
            "name":"Coke",
            "unitPrice":10.50,
            "discount":0.0,
            "productID":1
        }
    ],
    "totalAmount":10.50,
    "exludedVat":2.1,
    "vatPercentage":20
    <link rel="deleteLineItem" href="/invoice/1/deleteLineItem/123" />
    <link rel="updateLineItem" href="/invoice/1/updateLineItem/123" />
    <link rel="update" href="/invoice/1/update" />
    <link rel="delete" href="/invoice/1/delete" />
}

Open in new window


But your approach goes further, as you also seem to use HAL. Remember this is format. thus part of the second decisions.

In short: I don't think your solution is ideal. Cause imho it has some code smells. This is just my opinion. You can of course go this route.
Your question is about architecture. But it was pretty condensed. Maybe you missed to explain some parts you have in mind, but I cannot see right now. When "doing" software architecture, you need to switch your mindset. You need to ignore things. For example the structure of DTO's does not matter at all. Also the order of decisions means really, that later decisions must have no influence (in the meaning of dependency) on prior ones.

ETX/EOT.
Avatar of A

ASKER

So, lets say Invoice has several related resources, let say: Customer, BranchOffice, LineItems, Seller, etc, etc.

The solution would be to create a class with all these information. And for example, in the GET invoices/1 the API will return all these related resources with their full info.

So, no matter the client, the information it will get would be always the same with no possibility to avoid getting related resources that the client does not need.

When would you use words like expand/include?

Or are you suggesting to use some different class in the Invoice PUT with the required information?

Just in case, this is an enterprise API, mostly for internal use and controlled, its not a public API.
Avatar of A

ASKER

In resource expansion, I followed up StormPath guidelines: Take a look to their video at: https://www.youtube.com/watch?v=mZ8_QgJ5mbs in the minute 41:00 .
So, lets say Invoice has several related resources, let say: Customer, BranchOffice, LineItems, Seller, etc, etc.
Nope, there are some serious pitfalls.

Take an printed invoice. Does it has any related resource? No it has not. Cause any information on it, is not related. It is a materialized copy of the mentioned relations. Cause any of these resources can change, but the invoice must not change. Cause you must be able to reproduce the same invoice, you've mailed years ago to your customer, when an IRS audit happens.

These related resources are only relevant when you create an invoice, cause you need to copy data from them. Don't mix related resources (which require own endpoints) in REST/HATEOAS with collections/lists in HAL/JSON or even in a RDBMS or other database. Similar names, different concepts.
The only glimpse of those related resources in your invoice resource are pointers, which tell us, where the data is copied form.

GET /invoice/1
{  
    "id":1, 
    "sentToAddress": { "addressID":1, "name":"ACME", "street":"Dream Blvd. 13", "city":"Toon City" },
    "recipientOfServicesAddress": { "addressID":1, "name":"ACME", "street":"Dream Blvd. 13", "city":"Toon City" },
    "lineItems": [ { "productID":1, "price":10.50, "name":"Dynamyte", "amount":10.0 }],
      ..
}

Open in new window


Using collections in the result is for simpler usage and code handling. But this relates all to step three: Format.

So, no matter the client, the information it will get would be always the same with no possibility to avoid getting related resources that the client does not need.
And you're right, you don't always need this information. Remember step three, it's about format. Due to the above decisions we have only the option to reduce the data, when necessary. Or by using some other wording, the ability to compose the result. Thus a negative approach

GET /invoice/1?exclude=self%2Caddresses
{
    "id":1, 
    "lineItems": [ { "productID":1, "price":10.50, "name":"Dynamyte", "amount":10.0 }],
}

Open in new window


or a positive

GET /invoice/1?parts=addresses
{
    "id":1, 
    "sentToAddress": { "addressID":1, "name":"ACME", "street":"Dream Blvd. 13", "city":"Toon City" },
    "recipientOfServicesAddress": { "addressID":1, "name":"ACME", "street":"Dream Blvd. 13", "city":"Toon City" }
}

Open in new window

In resource expansion, I followed up StormPath guidelines:
Does not apply to invoices, due to the necessity to materialize this data in the invoice as already posted above.
Avatar of A

ASKER

Sorry ste5an, I selected the wrong example. I know that Invoice must be inmutable. I wanted to give you an example of a mutable resource to know how to handle CRUD with it and expansions. Thank you for your feedback!
This question needs an answer!
Become an EE member today
7 DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform.
View membership options
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.