Skip to content

Commit 06418a3

Browse files
committed
feat(Endpoint): implement dynamic endpoint resolution and add download script for regions.json
1 parent dc6a6d8 commit 06418a3

6 files changed

Lines changed: 1044 additions & 0 deletions

File tree

docs/endpoint-resolution.md

Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
# Region Endpoint Integration Specification
2+
3+
## Overview
4+
5+
Contentstack services are deployed across multiple cloud providers and geographic regions. SDKs must resolve service endpoints dynamically using the Contentstack Regions Registry rather than relying on hardcoded URLs.
6+
7+
This ensures:
8+
9+
- Consistent endpoint resolution across all SDKs
10+
- Automatic support for newly introduced regions
11+
- Automatic support for newly introduced services
12+
- Single source of truth for endpoint configuration
13+
- Elimination of region-specific host logic inside SDKs
14+
15+
---
16+
17+
## Regions Registry
18+
19+
All endpoint information is maintained in the Contentstack Regions Registry.
20+
21+
### Registry URL
22+
23+
```text
24+
https://artifacts.contentstack.com/regions.json
25+
```
26+
27+
The registry contains:
28+
29+
- Region identifiers
30+
- Region aliases
31+
- Default region information
32+
- Service endpoint mappings
33+
34+
### Example
35+
36+
```json
37+
{
38+
"regions": [
39+
{
40+
"id": "na",
41+
"alias": ["us", "aws-na"],
42+
"isDefault": true,
43+
"endpoints": {
44+
"contentDelivery": "https://cdn.contentstack.io",
45+
"contentManagement": "https://api.contentstack.io"
46+
}
47+
}
48+
]
49+
}
50+
```
51+
52+
---
53+
54+
## Endpoint Resolution Contract
55+
56+
All SDKs must expose a public endpoint resolution API.
57+
58+
```text
59+
getContentstackEndpoint(
60+
region,
61+
service,
62+
omitProtocol = false
63+
)
64+
```
65+
66+
### Parameters
67+
68+
| Parameter | Description |
69+
|-----------|-------------|
70+
| `region` | Region identifier or alias |
71+
| `service` | Service name |
72+
| `omitProtocol` | Removes protocol prefix from returned URL |
73+
74+
### Returns
75+
76+
- Service URL when a service is specified
77+
- Complete endpoint map when service is omitted
78+
79+
---
80+
81+
## Region Resolution Rules
82+
83+
Region matching must:
84+
85+
- Ignore case
86+
- Trim whitespace
87+
- Support aliases
88+
- Support both dash (`-`) and underscore (`_`) variants where defined
89+
90+
### Examples
91+
92+
| Input | Resolved Region |
93+
|--------|----------------|
94+
| `na` | `na` |
95+
| `us` | `na` |
96+
| `aws-na` | `na` |
97+
| `AWS_NA` | `na` |
98+
| `eu` | `eu` |
99+
| `azure-na` | `azure-na` |
100+
| `gcp-eu` | `gcp-eu` |
101+
102+
If no region is found:
103+
104+
```text
105+
Invalid region
106+
```
107+
108+
---
109+
110+
## Service Resolution Rules
111+
112+
SDKs must:
113+
114+
1. Locate the resolved region.
115+
2. Locate the service name within the region endpoints.
116+
3. Return the endpoint URL.
117+
118+
### Example
119+
120+
```text
121+
Region: eu
122+
Service: contentDelivery
123+
124+
Result:
125+
https://eu-cdn.contentstack.com
126+
```
127+
128+
If the service is unavailable:
129+
130+
```text
131+
Service not found
132+
```
133+
134+
---
135+
136+
## Supported Service Names
137+
138+
- `contentDelivery`
139+
- `contentManagement`
140+
- `graphqlDelivery`
141+
- `graphqlPreview`
142+
- `preview`
143+
- `auth`
144+
- `application`
145+
- `images`
146+
- `assets`
147+
- `automate`
148+
- `launch`
149+
- `developerHub`
150+
- `brandKit`
151+
- `genAI`
152+
- `personalizeManagement`
153+
- `personalizeEdge`
154+
- `composableStudio`
155+
- `assetManagement`
156+
157+
SDKs must not hardcode this list. The registry remains the source of truth.
158+
159+
---
160+
161+
## Registry Loading Requirements
162+
163+
Recommended priority:
164+
165+
1. In-memory cache
166+
2. Local registry file
167+
3. Registry download fallback
168+
169+
Examples:
170+
171+
- JavaScript SDK: Build-time download
172+
- PHP SDK: Install-time download with runtime fallback
173+
- Java SDK: Build-time download via Maven (`generate-resources` phase) with runtime fallback
174+
175+
---
176+
177+
## SDK Integration Requirements
178+
179+
```text
180+
Resolve Region
181+
182+
Resolve contentDelivery Endpoint
183+
184+
Configure SDK Host
185+
186+
Execute API Requests
187+
```
188+
189+
The SDK host must be configured using the resolved endpoint rather than a hardcoded hostname.
190+
191+
---
192+
193+
## Error Handling
194+
195+
| Scenario | Error |
196+
|-----------|--------|
197+
| Empty Region | Empty region provided |
198+
| Invalid Region | Invalid region |
199+
| Invalid Service | Service not found |
200+
| Registry Unavailable | Unable to load regions registry |
201+
202+
---
203+
204+
## Caching Requirements
205+
206+
Goals:
207+
208+
- Avoid repeated disk reads
209+
- Avoid repeated network requests
210+
- Improve endpoint lookup performance
211+
212+
Cache implementation is SDK-specific.
213+
214+
---
215+
216+
## Future Compatibility
217+
218+
SDK implementations must not:
219+
220+
- Hardcode endpoint URLs
221+
- Hardcode region mappings
222+
- Hardcode service name mappings
223+
224+
All endpoint information must originate from the Regions Registry.
225+
226+
---
227+
228+
## SDK Examples
229+
230+
### Java
231+
232+
```java
233+
import com.contentstack.utils.Endpoint;
234+
235+
// Get a specific service URL
236+
String cdaUrl = Endpoint.getContentstackEndpoint("eu", "contentDelivery");
237+
// → "https://eu-cdn.contentstack.com"
238+
239+
// Get the host without the https:// scheme
240+
String host = Endpoint.getContentstackEndpoint("eu", "contentDelivery", true);
241+
// → "eu-cdn.contentstack.com"
242+
243+
// Get all endpoints for a region
244+
Map<String, String> all = Endpoint.getContentstackEndpoint("eu");
245+
// → { "contentDelivery": "https://eu-cdn.contentstack.com", ... }
246+
247+
// Get all endpoints without the scheme
248+
Map<String, String> hosts = Endpoint.getContentstackEndpoint("eu", true);
249+
```
250+
251+
#### Region aliases
252+
253+
```java
254+
// All of the following resolve to the same NA region
255+
Endpoint.getContentstackEndpoint("na", "contentDelivery"); // → https://cdn.contentstack.io
256+
Endpoint.getContentstackEndpoint("us", "contentDelivery"); // → https://cdn.contentstack.io
257+
Endpoint.getContentstackEndpoint("aws-na", "contentDelivery"); // → https://cdn.contentstack.io
258+
Endpoint.getContentstackEndpoint("AWS_NA", "contentDelivery"); // → https://cdn.contentstack.io
259+
```
260+
261+
#### Available via `Utils` (proxy)
262+
263+
```java
264+
import com.contentstack.utils.Utils;
265+
266+
// Identical result to Endpoint.getContentstackEndpoint()
267+
String url = Utils.getContentstackEndpoint("eu", "contentDelivery");
268+
String host = Utils.getContentstackEndpoint("eu", "contentDelivery", true);
269+
Map<String, String> all = Utils.getContentstackEndpoint("eu");
270+
```
271+
272+
#### Error handling
273+
274+
```java
275+
try {
276+
Endpoint.getContentstackEndpoint("", "contentDelivery");
277+
} catch (IllegalArgumentException e) {
278+
// "Empty region provided. Please provide a valid region."
279+
}
280+
281+
try {
282+
Endpoint.getContentstackEndpoint("invalid", "contentDelivery");
283+
} catch (IllegalArgumentException e) {
284+
// "Invalid region: invalid"
285+
}
286+
287+
try {
288+
Endpoint.getContentstackEndpoint("na", "unknownService");
289+
} catch (IllegalArgumentException e) {
290+
// "Service \"unknownService\" not found for region \"na\""
291+
}
292+
```
293+
294+
#### Integration with Delivery SDK
295+
296+
```java
297+
import com.contentstack.sdk.Config;
298+
import com.contentstack.sdk.Contentstack;
299+
import com.contentstack.sdk.Query;
300+
import com.contentstack.sdk.QueryResult;
301+
import com.contentstack.sdk.QueryResultsCallBack;
302+
import com.contentstack.sdk.ResponseType;
303+
import com.contentstack.sdk.Stack;
304+
import com.contentstack.utils.Endpoint;
305+
306+
// 1. Resolve the host for the chosen region (omit https:// for setHost)
307+
String host = Endpoint.getContentstackEndpoint("eu", "contentDelivery", true);
308+
// → "eu-cdn.contentstack.com"
309+
310+
// 2. Wire it into a Config and create the Stack
311+
Config config = new Config();
312+
config.setHost(host);
313+
314+
Stack stack = Contentstack.stack("<API_KEY>", "<DELIVERY_TOKEN>", "<ENVIRONMENT>", config);
315+
316+
// 3. Fetch entries — all requests now go to the EU CDN
317+
Query query = stack.contentType("blog").query();
318+
query.find(new QueryResultsCallBack() {
319+
@Override
320+
public void onCompletion(ResponseType responseType, QueryResult queryResult, Error error) {
321+
if (error != null) {
322+
System.err.println(error.getErrorMessage());
323+
return;
324+
}
325+
queryResult.getResultObjects().forEach(entry ->
326+
System.out.println(entry.getTitle()));
327+
}
328+
});
329+
```
330+
331+
Change one string to switch regions — everything else stays the same:
332+
333+
```java
334+
// NA
335+
String host = Endpoint.getContentstackEndpoint("na", "contentDelivery", true);
336+
// → "cdn.contentstack.io"
337+
338+
// EU
339+
String host = Endpoint.getContentstackEndpoint("eu", "contentDelivery", true);
340+
// → "eu-cdn.contentstack.com"
341+
342+
// Azure NA
343+
String host = Endpoint.getContentstackEndpoint("azure-na", "contentDelivery", true);
344+
// → "azure-na-cdn.contentstack.com"
345+
346+
// GCP EU
347+
String host = Endpoint.getContentstackEndpoint("gcp-eu", "contentDelivery", true);
348+
// → "gcp-eu-cdn.contentstack.com"
349+
```
350+
351+
Read region from environment variable (recommended for production):
352+
353+
```java
354+
String region = System.getenv().getOrDefault("CONTENTSTACK_REGION", "na");
355+
356+
Config config = new Config();
357+
config.setHost(Endpoint.getContentstackEndpoint(region, "contentDelivery", true));
358+
359+
Stack stack = Contentstack.stack(
360+
System.getenv("CONTENTSTACK_API_KEY"),
361+
System.getenv("CONTENTSTACK_DELIVERY_TOKEN"),
362+
System.getenv("CONTENTSTACK_ENVIRONMENT"),
363+
config
364+
);
365+
```
366+
367+
#### Refreshing `regions.json`
368+
369+
```bash
370+
# Runs automatically on every Maven build (generate-resources phase)
371+
mvn generate-resources
372+
373+
# Or refresh manually
374+
bash scripts/download-regions.sh
375+
```

0 commit comments

Comments
 (0)