What this is about
In CDMS, a relation has two ends: company on the employee and employees on the company. Both describe the same connection. The field on the other side is called the other side or back reference.
flowchart LR
E["Employee Anna"] -->|"company"| C["Company Codamic"]
C -->|"employees"| E
CDMS maintains both ends
As soon as CDMS connects two objects, it sets both fields:
| Type | you set | CDMS also sets |
|---|---|---|
| 1:1 | person.mainPhone = P1 | P1.person = person |
| 1:n | company.employees = [Anna] | Anna.company = company |
| n:1 | anna.company = C | adds Anna to C.employees |
| n:m | group.roles = [R1] | adds the group to R1.groups |
The same happens when you remove a connection: if you remove Anna from employees, Anna.company becomes empty. So you can write every relation from the side that is more convenient for your use case. When you read, you see the connection on both sides.
Which side to write from?
When: You write the company with its list employees.
-
1Client→CDMSsends
PATCH /company/update/{id}with"employees": [{ "id": "anna…" }, { "id": "ben…" }] -
2CDMSsets
companyon Anna and Ben to this company -
3CDMSremoves all other employees from the list, because the list is the target state
Result: The company has exactly Anna and Ben. See Lists as target state.
When: You write an employee with their company.
-
1Client→CDMSsends
POST /employee/createwith"company": { "id": "c1…" } -
2CDMS→Databaselooks up the company
c1… -
3CDMSsets
companyand adds the employee to the company'semployees
Result: The other employees of the company stay untouched. For single assignments, this is the simpler way.
When: A person and their main phone.
Both sides work the same: person.mainPhone = { "id": … } or phone.person = { "id": … }. CDMS sets the other end in each case. To replace a child, you first remove the old one (field to null) and then set the new one. The easiest way is two steps.
Result: Exactly one connection between the two objects.
When: Groups and roles.
You can write group.roles or role.groups. Both ways create the same connection. Both are a target state for the list you send. The list on the other side only changes as far as this one connection is concerned.
Result: See Many-to-many through a join table.
Send the back reference along?
When you send back objects you have read, the back reference easily ends up in the request, for example company in every employee of the list employees:
PUT /api/rest/company/update/c1…
{
"data": {
"id": "c1…",
"companyname": "Codamic AG",
"employees": [
{ "id": "anna…", "firstname": "Anna", "lastname": "Schmidt",
"company": { "id": "c9…" } }
]
},
"response": ["+"]
}Afterwards Anna belongs to c1…, not to c9….
The company you write from wins.
There is no error.| Back reference in the child | points to | Result |
|---|---|---|
| missing | – | CDMS sets it to the parent object |
| present | the parent object | same as above, duplicated but harmless |
| present | a different object | is overwritten, the parent object wins |
| present | an unknown id | 404, the request fails |
So do not send the back reference. It adds nothing and can only go wrong.
PUT and the list of the other side
PUT describes the whole object, including its lists. This is true for every side of a relation. If you write a role with PUT and leave out its list groups, CDMS removes all connections of the role to groups.
- first read the role with
groups - send
groupsback with allids - otherwise all connections are gone
- send only the changed fields
- leave out
groups - the connections stay untouched
Pitfalls
What comes next
- What the flags allow: Relation types and recursive flags
- Lists in the payload: Lists as target state
- PUT and PATCH compared: PUT or PATCH? The null trap