Evolution of Update Capabilities

The requestedAction mechanism and the full patch-style update logic are already available in the current version of the API.

However, support for the replaceAll array-based syntax (e.g. replaceAll: ["CONTACTS"]) is part of an upcoming release. This documentation anticipates that change to help you understand the evolution of the API and prepare for its extended update capabilities.

When it comes to updating records in GraphQL, particularly using Hot Chocolate, the logic closely resembles the concept of the method in RESTful APIs.

Remember

In GraphQL, the act of modification, including updates, aligns with the concept of a mutation.
Mutations in GraphQL are carried out using the HTTP method, which is consistent with all other mutation and query actions. This ensures a standardized and intuitive approach to making changes to data within the GraphQL schema.

Granular Data Modification

The focus is on modifying only the specific fields that need to be changed, rather than sending the entire dataset.
This approach ensures efficiency and minimizes unnecessary data transfer.

In a typical update, you only need to send the fields that have changed.
This patch-style logic avoids sending unchanged fields, reducing payload size and improving performance.

For example, if you want to update just the vatNumber of a customer, your mutation can be minimal and efficient:

mutation ($values: CustomerUpdateGLDtoInput!) {  
  updateCustomer (input: $values) {
    id
  }
}
{
  "values": {
    "id": "{currentId}",
    "vatNumber": "FR11123456789"
  }
}

However, when dealing with nested sub-resources, such as a customer’s contacts, or the phones belonging to those contacts, the situation becomes more complex.

These nested structures are common in various parts of the API, for example:

Resource Type Nested Sub-Resources
Customers contacts, phones, emails, socialMedias, addresses, paymentTermLines
Suppliers contacts, phones, emails, socialMedias, addresses, paymentTermLines
Employees contacts, phones, emails, socialMedias
SalesQuotes salesQuoteLines
SalesOrders salesOrderLines
SalesDeliveryNotes salesDeliveryNoteLines
SalesInvoices salesInvoiceLines
(and other similar hierarchical structures)

The requestedAction field and the replaceAll flag play a crucial role when updating sub-resources, enabling precise control over whether to modify individual items or replace entire collections.

When dealing with nested sub-resources like contacts or phones, you have two possible strategies:

This hybrid model : patch by default, with optional replace by reset, allows flexibility depending on what the calling system is capable of providing.

Let’s look at two examples of how this can be handled:

  1. If your system can track exactly what changed (create, update, delete), then the requestedAction approach is recommended, as it allows precise control over each element.

    For example, consider the case of updating a customer who has multiple contacts, and each contact has multiple phones.
    In a single update operation, you might need to:

    • Delete a contact who has left the company
    • Create a new contact
    • Modify the phone number of an existing contact
    • Add or remove phones for specific contacts

    All these changes can be expressed together using the requestedAction field or simply relying on the presence or absence of id, as part of a patch-style mutation.

    Example using requestedAction

    This example demonstrates a patch-style update where:

    • The phones of an existing contact are individually updated (modify, delete, create)
    • One contact is deleted
    • One new contact is added
     {
       "id": "{customerId}",
       "contacts": [
         {
           // Modify existing contact's phones
           "id": "{contact1Id}",
           "requestedAction": "MODIFY", // Optional: can be omitted if id is present
           "phones": [
             {
               // Modify phone
               "id": "{phone1Id}",
               "requestedAction": "MODIFY", // Optional: can be omitted if id is present
               "number": "01 23 45 67 89"
             },
             {
               // Delete phone
               "id": "{phone2Id}",
               "requestedAction": "DELETE" // Required to delete
             },
             {
               // Create new phone
               "requestedAction": "CREATE", // Optional: can be omitted if id is absent
               "number": "06 07 08 09 10",
               "type": "MOBILE"
             }
           ]
         },
         {
           // Delete contact
           "id": "{contact2Id}",
           "requestedAction": "DELETE" // Required to delete
         },
         {
           // Create new contact
           "requestedAction": "CREATE", // Optional: can be omitted if id is absent
           "name": "New Contact",
           "phones": [
             {
               "requestedAction": "CREATE", // Optional: can be omitted if id is absent
               "number": "05 55 55 55 55",
               "type": "LANDLINE"
             }
           ]
         }
       ]
     }
    
  2. However, if your system does not track precisely what changed, for example if it only knows the final expected list of contacts and phones, then replaceAll becomes a better fit.
    In that case, it’s easier to clear the previous values and recreate the full structure in one step.
    You can use the replaceAll field as an array to specify which collections should be fully replaced.

    For example:

    • When updating a CUSTOMER, use replaceAll: ["CONTACTS", "ADDRESSES"] to replace all contacts and addresses.
      This will also implicitly replace all sub-collections within each contact, such as PHONES, EMAILS, and SOCIAL_MEDIAS.
    • Or when updating a specific CONTACT inside a customer, use replaceAll: ["PHONES", "EMAILS", "SOCIAL_MEDIAS"] to replace only the sub-collections of that contact.
    Example using replaceAll

    This example replaces the entire list of contacts and their sub-collections:

    • All existing contacts on the customer are removed
    • The list is fully replaced by two new contacts
    • Because replaceAll includes CONTACTS, all sub-collections within each contact such as PHONES, EMAILS, and SOCIAL_MEDIAS are also fully replaced
    • Collections are declared as arrays, using the same structure as in creation
     {
       "id": "{customerId}",
       "replaceAll": ["CONTACTS"], // specify which top-level collections to fully replace
       "contacts": [
         {
           // Recreate contact "Alice" with new sub-collections
           "name": "Alice",
           "phones": [
             {
               "number": "06 07 08 09 10",
               "type": "MOBILE"
             }
           ],
           "emails": [
             {
               "emailAddress": "[email protected]",
               "usage": "WORK"
             }
           ]
         },
         {
           // Recreate contact "Bob" with new sub-collections
           "name": "Bob",
           "phones": [
             {
               "number": "01 23 45 67 89",
               "type": "LANDLINE"
             }
           ],
           "socialMedias": [
             {
               "name": "LinkedIn",
               "link": "https://linkedin.com/in/bob"
             }
           ]
         }
       ]
     }
    

Summary Table: Sub-Resource Update Strategies

Scenario id requestedAction Behavior
New item not provided not provided or CREATE Create
Modify existing item provided not provided or MODIFY Update
Delete existing item provided DELETE (required) Delete
Recreate everything (replaceAll) not provided not provided Full replacement

requestedAction: Rules and Requirements

This approach is suitable when the system replicating the data knows the exact changes to apply and can identify each sub-resource by its id.
Typically, this means the original application has stored the identifiers and is able to send only what was modified, added, or deleted.

When is requestedAction necessary?

Example: Updating a contact’s phones using requestedAction

Effect:

  • The phone with id = {phone1Id} is modified.
  • The phone with id = {phone2Id} is deleted.
  • A new phone is created.
  • The rest of the contact and other phones remain unchanged.
{
  "id": "{customerId}",
  "contacts": [
    {
      "id": "{contactId}",
      "phones": [
        {
          // Modify phone: 'requestedAction' is optional when 'id' is present.
          // Without 'requestedAction', it defaults to MODIFY.
          "id": "{phone1Id}",
          "number": "01 23 45 67 89"
        },
        {
          // Delete phone: 'requestedAction: DELETE' is mandatory to explicitly remove this phone.
          "id": "{phone2Id}",
          "requestedAction": "DELETE"
        },
        {
          // Create new phone: no 'id' means this is treated as a new entry.
          // 'requestedAction' is optional and defaults to CREATE when 'id' is absent.
          "number": "06 07 08 09 10",
          "type": "MOBILE"
        }
      ]
    }
  ]
}

replaceAll: Rules and Requirements

This approach is ideal when the system replicating the data does not track individual changes or identifiers, but only knows the final expected state.
For instance, it may detect that the customer has changed but cannot identify what exactly changed in the contact or phone lists. In such cases, fully replacing the collection is often simpler and safer.

The replaceAll flag is a list of top-level fields declared at the root of the mutation payload.
It defines which collections should be fully replaced by the incoming data.

1. Top-level replaceAll (e.g. replaceAll: ["CONTACTS"])

2. Mixed logic with partial updates

If replaceAll does not include a given collection (e.g. contacts), then:

Rules to keep in mind:

Example 1: Replacing all contacts, addresses and their sub-resources

Effect:

  • All existing contacts and addresses are deleted.
  • Each contact is recreated with its own sub-resources (phones, emails, socialMedias).
  • There is no need to specify replaceAll inside contacts or for their nested collections, the root-level instruction is enough.
{
  "id": "{customerId}",
  "replaceAll": ["CONTACTS", "ADDRESSES"], // Fully replace contacts and addresses
  // Recreate addresses
  "addresses": [
    {
      "firstLine": "123 Generic street",
      "city": "Generic City",
      "zipCode": "12345",
      "countryIsoCodeAlpha2": "FR"
    }
  ],
  "contacts": [
    {
      // Recreate contact "Alice" with new sub-collections
      "name": "Alice",
      "phones": [
        {
          "number": "06 07 08 09 10",
          "type": "MOBILE"
        }
      ],
      "emails": [
        {
          "emailAddress": "[email protected]",
          "usage": "WORK"
        }
      ]
    },
    {
      // Recreate contact "Bob" with new sub-collections
      "name": "Bob",
      "phones": [
        {
          "number": "01 23 45 67 89",
          "type": "LANDLINE"
        }
      ],
      "socialMedias": [
        {
          "name": "LinkedIn",
          "link": "https://linkedin.com/in/bob"
        }
      ]
    }
  ]
}
Example 2: Replacing only the phones, emails and social medias of a specific contact

Effect:

  • The contact itself is not replaced, only its sub-collections are.
  • This assumes the contact is identified (typically by id) and updated using requestedAction: MODIFY.
  • The replaceAll flag is declared inside the contact using "replaceAll": ["PHONES", "EMAILS", "SOCIAL_MEDIAS"].
{
  "id": "{customerId}",
  "contacts": [
    {
      "id": "{contactId1}",
      "requestedAction": "MODIFY",
      "replaceAll": ["PHONES", "EMAILS", "SOCIAL_MEDIAS"], // Replace only these sub-collections
      // Recreate the current contact's phones
      "phones": [
        {
          "number": "06 99 88 77 66",
          "type": "MOBILE"
        },
        {
          "number": "01 11 22 33 44",
          "type": "LANDLINE"
        }
      ],
      // Recreate the current contact's emails
      "emails": [
        {
          "emailAddress": "[email protected]",
          "usage": "INVOICES"
        }
      ],
      //Recreate the current contact's social medias
      "socialMedias": [
        {
          "name": "X",
          "link": "https://x.com/alice"
        }
      ]
    }
  ]
}