SAP CAP Blog Posts
cancel
Showing results for 
Search instead for 
Did you mean: 

There's a problem that comes up on almost every multi-project SAP BTP engagement, and I've seen teams solve it in some really messy ways.

You have two CAP projects. One of them — let's call it Project A — owns a set of shared constants, configuration parameters, maybe a job run log. Project B needs to read that data. So what do people do? They copy the table. Or they create an API. Or they hardcode values that really shouldn't be hardcoded.

All of these work. None of them are clean.

The proper solution is HDI cross-container synonyms. Once it's set up, Project B can query Project A's HANA tables directly — same SQL, same CAP service code, no replication, no drift between environments. It looks and feels like a local table. It just happens to live in someone else's HDI container.

I put together a Tech Bite on this recently and figured it's worth writing up properly, because the documentation is scattered and the first time you set it up you will almost certainly hit at least one of the three errors I mention at the end.


The thing nobody explains first: why it's complicated at all

HDI containers are isolated. That's the point of them — each CAP project gets its own schema, its own deploy lifecycle, its own access controls. But that isolation also means Container B can't just say "I want to read from Container A's schema" — because the schema name is dynamically assigned at deployment time. You can't hardcode it.

So SAP's solution is a User-Provided Service, or UPS. It's a simple Cloud Foundry construct — basically a key-value store with no runtime component — that carries the schema name of Project A. You register it with the -t hana tag, declare it in Project B's mta.yaml, and the HDI deployer picks it up during deployment and injects the correct schema wherever you've referenced it.

That's it. That's the whole trick. Everything else — the synonym files, the config files, the grants — is just plumbing on top of that core idea.


What you're actually building in Project B

Once you understand what the UPS is doing, the rest makes sense. You need four types of files:

.hdiconfig tells the deployer which plugins to use. You need two of these — one in db/src/ and one in db/cfg/. It's a small JSON file, easy to get wrong by forgetting one of the two locations.

.hdbsynonym is where you define the local alias. So instead of referencing PROJECT_A_SCHEMA.CONSTANTS_TABLE (which you can't hardcode anyway), you just write CONSTANTS_TABLE and map it to the actual technical object name in Project A. This lives in db/src/.

.hdbsynonymconfig is the file that connects the synonym to the UPS. This is what makes the schema resolution dynamic. It lives in db/cfg/ — deliberately separate from db/src/ to avoid deployment conflicts. The key thing here is that the value you put in schema.configure must start with exactly the same string you used as the key in mta.yaml. More on this when I get to the errors.

.hdbgrants is the access control piece. Project B's container needs to be explicitly granted a role from Project A. That role needs to already exist over in Project A — it's not something you create in Project B. Your Project A team needs to have a role set up that grants SELECT on the tables you need.


The setup, briefly

On the Cloud Foundry side, you create the UPS once:

cf create-user-provided-service ProjectA_UPS \
  -p '{"schema": "YOUR_PROJECT_A_SCHEMA"}' -t hana

Then in Project B's mta.yaml, you declare it as an existing service resource and bind it to the db-deployer module under SERVICE_REPLACEMENTS:

- name: ProjectA-UPS-resource
  group: SERVICE_REPLACEMENTS
  properties:
    key: ProjectA_UPS
    service: ~{service-name}

Your synonym config then references it like this:

"schema.configure": "ProjectA_UPS/schema"

And once everything deploys, your CAP service code doesn't need to know any of this happened. You just query it:

const db = await cds.connect.to("db");
const results = await db.run(
  `SELECT * FROM "CONSTANTS_TABLE" WHERE "PARAM" = ?`, [value]
);

Clean. No different from querying a local table.


The three errors you will probably hit

I'm not going to pretend this always works first time. Here's what goes wrong most often:

"synonym config refers to unknown service replacement" — This one is almost always a key mismatch. The string you put as key in mta.yaml and the prefix before /schema in your .hdbsynonymconfig need to be identical. Character for character. Check both files side by side.

"insufficient privilege" — The role you referenced in .hdbgrants doesn't exist in Project A, or it exists but doesn't include SELECT on the tables you're trying to reach. You'll need to go back to whoever owns Project A and check what roles are actually deployed there. HANA DB Explorer is your friend here.

"object not found" — The object name in your .hdbsynonym is wrong. The technical name of a table in HDI usually follows a namespace pattern with underscores replacing dots — something like HR_HTR_UTILITY_OBJECTS_CONSTANTS_TABLE. The only reliable way to find the exact name is to look it up directly in HANA DB Explorer under Project A's schema.


One thing worth knowing for local development

When you run cds watch locally, you're using SQLite — there's no HDI container, no synonym. So you need a workaround for local testing.

The simplest approach is to create a CDS entity with @CDS.persistence.exists that mirrors the external table's structure, and seed it with a CSV file. It won't be connected to Project A's live data, but it lets you develop and test the rest of your logic without needing a full BTP deployment every time.


Worth the setup

The first time you wire this up it feels like a lot of moving parts. And honestly, it is. But once it's done, it just works — and it works cleanly. No sync jobs, no API calls for what is essentially a config lookup, no risk of the two sides drifting apart over time.

If your project has shared reference data living in another team's HDI container, this is the right way to access it.

Happy to answer questions in the comments if you hit something I haven't covered here.

#SAP #SAPCAP #SAPHANA #BTP #SAPDevelopment #HDI #CloudFoundry #PwC

7 Comments