DJ Adams

Modules, modularity & reuse in CDS models - part 4 - from passive to active with @capire/common

Starting with a simple use of the published @qmacro/common reuse module, I then turn to @capire/common for a first look at what I call an "active" reuse module.

(Get to all the parts in this series via the series post.)

In the previous part we published @qmacro/common. Let's start out here by creating a new simple CAP Node.js project and bringing in that @qmacro/common package; not only will this show that we really do have a publicly available & shareable reuse package, but observing what happens will also underline its simple and passive nature.

Creating a simple host project

Instead of turning back to the "host" CAP Node.js project that we created in part 1 with the packages/@qmacro/common workspace, let's create a fresh host project to have a clean context into which to bring each reuse module. First, let's set up1 use-qmacro for qmacro/common:

cds init \
  --add tiny-sample \
  use-qmacro \
  && cd $_ \
  && npm install

The CDS model artifacts in the "tiny-sample" facet are still simple enough, which is what we want. Starting the server now (with cds watch) will allow us to see what happens (and, perhaps a little more telling, what does not happen) in subsequent steps:

[cds] - loaded model from 3 file(s):

  srv/cat-service.cds
  node_modules/@sap/cds/srv/outbox.cds
  db/schema.cds

[cds] - connect to db > sqlite { url: ':memory:' }
  > init from db/data/my.bookshop-Books.csv
/> successfully deployed to in-memory database.

Noteworthy here are the files which are being loaded for the CDS model, and the initial data load from a CSV file that was found.

@qmacro/common

It's now time to start exploring what it's like to actually use a reuse package. We'll start here with @qmacro/common and then later take a first look at @capire/common, so that we can compare what happens.

Adding @qmacro/common

In a second terminal session (so we can monitor the cds watch output in the first) let's install2 the @qmacro/common reuse module:

npm add @qmacro/common

This emits something like this3:

added 1 package, and audited 115 packages in 858ms

found 0 vulnerabilities

More importantly, it causes the CAP server to restart. But so far, there are no new log lines of interest - the same 3 files as before are loaded for the CDS model, and the same single CSV file.

Using @qmacro/common

Let's now make use of this simple passive reuse package by importing4, as a first step:

using qmacro from '@qmacro/common'; // <--

namespace my.bookshop;

entity Books {
  key ID    : Integer;
      title : String;
      stock : Integer;
}

As soon as the file is saved with this new using directive, the CAP server restarts, and a new CDS file appears in the list of sources used to create the CDS model:

[cds] - loaded model from 4 file(s):

  srv/cat-service.cds
  node_modules/@sap/cds/srv/outbox.cds
  db/schema.cds
  node_modules/@qmacro/common/index.cds

It's the index.cds file that we created in part 2, nice!

We haven't made use of the qmacro.common.T type yet, but the compiler still loads the reuse package contents (whatever index.cds contains and / or refers to).

But the important thing to observe with this "passive" reuse package is that it's only once we explicitly refer to it in our model definitions that anything happens.

To complete the test of this simple reuse package, we can of course add a further element to the Books entity definition like this, defined with this imported type:

using qmacro from '@qmacro/common';

namespace my.bookshop;

entity Books {
  key ID    : Integer;
      title : String;
      stock : Integer;
      newel : qmacro.common.T; // <--
}

but this has no further effect on how the package is used or behaves.

So far, so good! Let's now compare that with a first look at using @capire/common.

Creating a second host project

To keep things clean and separate, let's now create a second host project just like the first, this time called use-capire, and start up the CAP server straight after:

cds init \
  --add tiny-sample \
  use-capire \
  && cd $_ \
  && npm install \
  && cds watch

If you're playing along at home, make sure you create this parallel to use-qmacro, not within it, of course.

From the cds watch output, we see the same output as previously, at the same stage, with use-qmacro, most notably that the CDS model is composed of 3 files:

[cds] - loaded model from 3 file(s):

  srv/cat-service.cds
  node_modules/@sap/cds/srv/outbox.cds
  db/schema.cds

@capire/common

In the same way as with @qmacro/common, let's this time add @capire/common to the project.

Associating the scope with the registry

Just like how we associated @qmacro with the GitHub Packages NPM registry in the previous part, we'll need to do the same for @capire, by adding another line to our ~/.npmrc file5:

@capire:registry=https://npm.pkg.github.com

Adding @capire/common

Now we can add it:

npm add @capire/common

We get similar output to this as before, but significantly, when the CAP server restarts, we see this:

[cds] - loaded model from 7 file(s):

  srv/cat-service.cds
  node_modules/@sap/cds/srv/outbox.cds
  node_modules/@capire/common/index.cds
  node_modules/@capire/common/regions.cds
  node_modules/@capire/common/currencies.cds
  db/schema.cds
  node_modules/@sap/cds/common.cds

Waitwhat?

Active vs passive

What's going on? Where did they come from? Why are they appearing, even when we haven't imported anything into the use-capire model yet?

This is our first glimpse of a side effect coming from the fact that -- unlike @qmacro/common, which is passive -- @capire/common is active6. In other words, the reuse package will do things, explicitly and sometimes immediately, as soon as we've added it.

How does that work? We'll find out in the next part!

Footnotes

  1. While things would be fine without an npm install at this setup stage (using the globally installed @sap/cds package rather than a project-local one), we'll use npm install here mostly for cosmetics - references in the log output will be to project-local resources rather than global ones which have far longer paths; also, we'll be running a package install shortly anyway, so the main part of the install work might as well be done now.

  2. Remember that you'll have to have the appropriate settings in an npmrc file; see the Preparing to publish the package section of the previous part for a reminder about this. Basically, you'll need something like this, say, in ~/.npmrc:

    @qmacro:registry=https://npm.pkg.github.com
    //npm.pkg.github.com/:_authToken=A-CLASSIC-TOKEN-WITH-AT-LEAST-READ-PACKAGES-SCOPE
  3. The use of the top level name only (i.e. just qmacro) in the using directive is deliberate here, just to show a different way of referencing the scope and subsequent use in definition positions. See the Namespaces section of Capire's CDL topic for further details.

  4. I have fund=false in my ~/.npmrc file so the "... packages are looking for funding ..." messages are suppressed.

  5. There's an entire blog post on Using @capire modules from GitHub Packages, in case you're interested.

  6. The terms "active" and "passive" aren't official CAP terms, they're just what I have come up wih to distinguish reuse package types.