cancel
Showing results for 
Search instead for 
Did you mean: 

CAP Multitenancy - Access to data in handlers

former_member194549
Contributor

Hi christian.paulus

i have a problem accessing data from a custom handler when multitenancy is enabled.

In an own handler it is not possible to read data from the database e.g. via cds.run(SELECT.from(Entity)) or SELECT.from(Entity), because the following error is returned:

{
    "error": {
        "code": "500",
        "message": "There is no instance for tenant \"anonymous\""
    }
}

The following implemented handler will cause this error

this.before("READ", "Books", async req => {
    //  does not work, returns There is no instance for tenant \"anonymous\"
    const authors1 = await cds.run(SELECT.from(Authors));

    if (1 === 2) {
        return true;
    }
});

If you delete cds.run, the data of the books will be returned.

Is it no longer possible to access the database this way when using the Multitenancy feature?
Or am I doing something wrong?

Example project for reproduction: GitHub

The Tenant onboarding worked without any problems.

Best Regards
Simon

View Entire Topic
christian_paulus
Employee
Employee
0 Kudos

Hey Simon,

it is exactly as Sergei wrote. For calling other services and handling the multitenancy it is important to use cds transactions as described here: https://cap.cloud.sap/docs/node.js/api#srv-tx

This ensures the client credentials flow and proper tenant isolation.

We should really document this better for multitenancy...

Example applied to the bookshop:

 const {Books} = cds.entities

 // Reduce stock of ordered books
  srv.before ('CREATE', 'Orders', async (req) => {
    const order = req.data
    if (!order.amount || order.amount <= 0)  return req.error (400, 'Order at least 1 book')
    const tx = cds.transaction(req)
    const affectedRows = await tx.run (
      UPDATE (Books)
        .set   ({ stock: {'-=': order.amount}})
        .where ({ stock: {'>=': order.amount},/*and*/ ID: order.book_ID})
    )
    if (affectedRows === 0) return req.error (409, 'Sold out, sorry', 'amount')
  })
...

Best regards,

Christian

former_member194549
Contributor

Hi Christian,

thank you for your answer.
Now the access to the data works like a charm.

Since we do have some extra express rest endpoints where we also want to read via cds from the database, the following might be interesting for someone:

An express request cannot be used as transaction context. Here you first have to create a cds.User instance which is passed to cds.Request. This request can then be used as a transaction context.

const context = someJWTDecodeFunction(jwt)
const user = new cds.User({id: context.email, _req: req, tenant: context.tenant, attr: context.attributes});
const request = new cds.Request({user: user});


const tx = cds.transaction(request)
const authors = await tx.run(SELECT.from(Authors));

Best regards
Simon

SamueleBarzaghi
Participant
0 Kudos

Hi Simon,

I'm the man interested in your suggestion! used! thank you very much!

Continue knowledge sharing...

Transaction without express request, routine called one per minute with technical user:

cds.on("served", async (app) => {
    setTimeout(checkMovementsStatus, 60000);
});

async function checkMovementsStatus() {
    const technicalUser = new cds.User({
        id: "...email...",
        tenant: "...customer_tenantid...",
    });

    const request = new cds.Request({ user: technicalUser });

    const tx = cds.transaction(request);

    const { HandlingUnitsRawMovements } = cds.entities;

    const select = SELECT.from(HandlingUnitsRawMovements).columns("ID", "CP_ID");

    try {
        const movements = await tx.run(select);
        ...
        tx.commit();
    } catch (error) {
        console.error("🤢", error);
    }

    setTimeout(checkMovementsStatus, 60000);
}