Skip to content

Commit 3682ec2

Browse files
akoclaude
andcommitted
docs: add proposal for bulk external action support from OData contracts
Outlines the design for CREATE EXTERNAL ACTIONS FROM, covering complex type parsing, NPE generation, response tree depth, and phased implementation plan. Needs Studio Pro BSON investigation before coding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2d2c747 commit 3682ec2

1 file changed

Lines changed: 199 additions & 0 deletions

File tree

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Proposal: Bulk External Action Support from OData Contracts
2+
3+
## Problem
4+
5+
Issue [#143](https://github.com/mendixlabs/mxcli/issues/143) requests importing all entities **and actions** from a consumed OData service. `CREATE EXTERNAL ENTITIES FROM Module.Service` now handles bulk entity creation. This proposal covers the action side: generating the artifacts needed to call external OData actions.
6+
7+
## Current State
8+
9+
| Capability | Status |
10+
|------------|--------|
11+
| Browse contract actions |`SHOW CONTRACT ACTIONS FROM Module.Service` |
12+
| Describe contract action |`DESCRIBE CONTRACT ACTION Module.Service.Action` |
13+
| Call external action in microflow |`CALL EXTERNAL ACTION Module.Service.Action(...)` |
14+
| Bulk-create external entities |`CREATE EXTERNAL ENTITIES FROM Module.Service` |
15+
| Bulk-create action support artifacts | ❌ Not implemented |
16+
| Parse complex types from $metadata | ❌ Not implemented |
17+
| Response tree depth handling | ❌ Not modeled |
18+
19+
## What Calling an External Action Requires
20+
21+
### Artifacts per action
22+
23+
1. **Parameter entities** — for each action parameter with a complex type (non-primitive), a non-persistent entity (NPE) or external entity must exist in the domain model
24+
2. **Return type entity** — if the action returns a complex type, the corresponding entity must exist
25+
3. **The microflow** — with `CALL EXTERNAL ACTION` wired up with parameter mappings
26+
27+
### Type classification
28+
29+
OData `$metadata` defines several type kinds that map differently to Mendix:
30+
31+
| OData type | Mendix equivalent | Example |
32+
|------------|-------------------|---------|
33+
| `Edm.String`, `Edm.Int32`, etc. | Primitive attribute types | `Edm.String``String(200)` |
34+
| Entity type (has entity set) | External entity | `TripPin.Person` → external entity |
35+
| Complex type (no entity set) | Non-persistent entity | `TripPin.City` → NPE with `CityName`, `Region` |
36+
| `Collection(Namespace.Type)` | List of entity | `Collection(TripPin.Trip)` → list parameter |
37+
| Enum type | Enumeration | `TripPin.PersonGender` → enumeration |
38+
39+
### Response tree depth (key complexity)
40+
41+
When Studio Pro configures an external action call, it allows the user to choose **how deep** the response should be deserialized:
42+
43+
- **Top-level only** — only the returned entity's own attributes
44+
- **With associations** — also deserialize associated complex objects / navigation properties
45+
46+
This is controlled in the BSON by the `VariableDataType` field on `CallExternalAction`, which specifies both the return type and what parts of the object graph to materialize. The exact mechanism needs investigation by creating reference examples in Studio Pro.
47+
48+
**Open questions:**
49+
- How is the depth/scope stored in BSON? Is it a separate field or encoded in `VariableDataType`?
50+
- Can it reference navigation properties selectively (like `$expand` in OData)?
51+
- Does it affect which NPEs need to exist? (i.e., if you only request top-level, do you still need NPEs for nested complex types?)
52+
53+
## Gaps to Address
54+
55+
### 1. Complex type parsing in EDMX
56+
57+
`sdk/mpr/edmx.go` currently parses entity types, enum types, and actions from `$metadata` XML. It does **not** parse `<ComplexType>` elements. These are required because:
58+
59+
- Action parameters often use complex types as input
60+
- Action return types may be complex types
61+
- Navigation properties on entity types may reference complex types
62+
63+
**Work needed:**
64+
- Add `EdmComplexType` struct (similar to `EdmEntityType` but without key properties or entity set)
65+
- Parse `<ComplexType>` in `ParseEdmx()`
66+
- Store on `EdmSchema.ComplexTypes`
67+
68+
### 2. Type resolution for action parameters
69+
70+
`EdmAction.Parameters[].Type` can be:
71+
- A primitive (`Edm.String`) — no entity needed
72+
- A qualified entity type (`Namespace.Customer`) — needs external entity
73+
- A qualified complex type (`Namespace.Address`) — needs NPE
74+
- A collection (`Collection(Namespace.Item)`) — needs entity + list parameter type
75+
- An enum (`Namespace.Status`) — needs enumeration
76+
77+
Resolution requires looking up whether a type name refers to an entity type (has entity set), complex type, or enum type in the schema.
78+
79+
### 3. Response tree depth
80+
81+
The core design question: when generating action call scaffolding, how do we handle the response tree?
82+
83+
**Option A: Top-level only (simplest)**
84+
Generate only the immediate return type entity. Users manually expand if needed.
85+
86+
**Option B: Full tree (Studio Pro default)**
87+
Walk navigation properties of the return type, recursively create NPEs/external entities for all reachable types.
88+
89+
**Option C: User-controlled depth**
90+
```sql
91+
CREATE EXTERNAL ACTIONS FROM Module.Service DEPTH 1; -- top-level only
92+
CREATE EXTERNAL ACTIONS FROM Module.Service DEPTH 2; -- include direct associations
93+
```
94+
95+
**Recommendation:** Start with Option A, document that users can add depth manually. Investigate Studio Pro's BSON to understand the exact storage before implementing deeper options.
96+
97+
### 4. CallExternalAction BSON completeness
98+
99+
Current parser/writer for `CallExternalAction` may be missing fields that Studio Pro writes. Specifically:
100+
- `VariableDataType` — not currently parsed (controls return type inference)
101+
- Response depth/scope fields — unknown, needs BSON investigation
102+
103+
## Proposed Syntax
104+
105+
### Phase 1: Entity/type scaffolding only
106+
107+
```sql
108+
-- Create NPEs and enumerations for all action parameter/return types
109+
-- that don't already exist as entities in the project
110+
CREATE EXTERNAL ACTIONS FROM Module.Service;
111+
112+
-- Filter to specific actions
113+
CREATE EXTERNAL ACTIONS FROM Module.Service ACTIONS (GetTrips, CreateTrip);
114+
115+
-- Into a different module
116+
CREATE EXTERNAL ACTIONS FROM Module.Service INTO Integration;
117+
118+
-- Idempotent
119+
CREATE OR MODIFY EXTERNAL ACTIONS FROM Module.Service;
120+
```
121+
122+
This would:
123+
1. Parse all actions from cached `$metadata`
124+
2. For each action, resolve parameter types and return type
125+
3. Create NPEs for complex types that don't have entity sets
126+
4. Create external entities for entity types that aren't already imported
127+
5. Create enumerations for enum types
128+
6. Output a summary of what was created
129+
130+
It would **not** generate microflows — that's the user's job (or a Phase 2 feature).
131+
132+
### Phase 2: Microflow generation (future)
133+
134+
```sql
135+
-- Generate stub microflows that call each action
136+
CREATE EXTERNAL ACTION MICROFLOWS FROM Module.Service;
137+
```
138+
139+
### Phase 3: DESCRIBE FORMAT mdl for actions (future)
140+
141+
```sql
142+
-- Generate a complete MDL script for calling an action
143+
DESCRIBE CONTRACT ACTION Module.Service.GetTrips FORMAT mdl;
144+
```
145+
146+
Would output something like:
147+
148+
```sql
149+
-- Required NPE for return type
150+
CREATE NON-PERSISTENT ENTITY Module.Trip (
151+
TripId: Long,
152+
Name: String(200),
153+
Description: String(500)
154+
);
155+
156+
-- Microflow to call the action
157+
CREATE MICROFLOW Module.ACT_GetTrips($PersonId: String) RETURNS List of Module.Trip
158+
BEGIN
159+
$Result = CALL EXTERNAL ACTION Module.Service.GetTrips(personId = $PersonId);
160+
RETURN $Result;
161+
END;
162+
```
163+
164+
## Investigation Required Before Implementation
165+
166+
Before coding, these questions need answers from Studio Pro reference examples:
167+
168+
1. **Create a reference project** with a consumed OData service (e.g., TripPin) that has actions with complex type parameters and return types
169+
2. **Inspect the BSON** for `CallExternalAction` to understand:
170+
- How `VariableDataType` encodes the return type
171+
- Whether there's a depth/expand field for response tree
172+
- How bound actions differ in storage
173+
3. **Inspect the NPEs** that Studio Pro creates for complex types:
174+
- Are they plain `DomainModels$EntityImpl` with `Persistable: false`?
175+
- Do they have a special `Source` field (like external entities have `Rest$ODataRemoteEntitySource`)?
176+
- How are associations between NPEs and external entities stored?
177+
4. **Inspect enumerations** from OData:
178+
- Do they use `Rest$ODataRemoteEnumerationSource`?
179+
- How are they linked back to the consumed service?
180+
181+
## Implementation Order
182+
183+
1. **Parse complex types** from `$metadata` (`sdk/mpr/edmx.go`)
184+
2. **Type resolver** — given a qualified type name, determine if it's entity/complex/enum/primitive
185+
3. **Phase 1 executor**`CREATE EXTERNAL ACTIONS FROM` creates NPEs, enums, external entities
186+
4. **BSON investigation** — Studio Pro reference project for CallExternalAction fields
187+
5. **Phase 2**`DESCRIBE CONTRACT ACTION ... FORMAT mdl` generates complete MDL
188+
6. **Phase 3** — microflow generation
189+
190+
## Related Files
191+
192+
- `sdk/mpr/edmx.go` — EDMX parsing (needs ComplexType support)
193+
- `sdk/mpr/parser_microflow_actions.go` — CallExternalAction parser
194+
- `sdk/mpr/writer_microflow_actions.go` — CallExternalAction writer
195+
- `sdk/microflows/microflows_actions.go` — CallExternalAction struct
196+
- `mdl/executor/cmd_contract.go` — contract browsing + CREATE EXTERNAL ENTITIES
197+
- `mdl/executor/cmd_odata.go` — OData CRUD commands
198+
- `mdl/ast/ast_odata.go` — OData AST nodes
199+
- `docs/11-proposals/odata-services-proposal.md` — original OData proposal (Phase 3 section)

0 commit comments

Comments
 (0)