Power PatternsStatic Joins

Static Join on Properties

Materialized Joins for Denormalized, Read-Optimized Data. (PropertyStaticJoin)

1. Scope

PropertyStaticJoin (often referred to as static join) is a pattern on DataProperty that lets you:

This is effectively a materialized join:

  • It reads a field from a related DataObject (possibly in another service).

  • It writes that value into a property of the current object.

  • It does this automatically during create/update flows.

  • The value is stored in the current object and can be queried directly (DB or Elasticsearch) without additional joins.

Static joins are ideal when:

  • The joined data is not expected to change frequently, or the cost of being slightly stale is acceptable.

  • You want to avoid complex runtime joins in Business APIs or BFF DataViews.

  • You want a field (e.g. clientId, statusName, categoryName) to be directly searchable or filterable in Elasticsearch or DB.


2. Conceptual Overview

2.1 Why Static Join?

Consider this typical pattern:

  • You have an object Order with userId.

  • You also need the user’s clientId (tenant) inside each order to:

    • Filter orders by client without joining to user.

    • Apply client-level analytics and reporting.

With static join:

  • You configure Order.clientId to be static-joined from User.clientId.

  • When an order is created or updated:

    • Mindbricks reads the relevant User record.

    • It copies clientId from User to Order.clientId.

  • Now Order has clientId as a direct field, and no additional join is needed at read time.

Static join is also useful for:

  • Storing parent’s tenant ID to children (e.g., projectIdproject.clientId).

  • Denormalizing frequently used labels (e.g. countryName, categoryName).

  • Pre-populating derived routing data (e.g. organizationRegion).

2.2 Static vs Dynamic Joins

  • Static join:

    • Value is materialized and stored on the source object at write time.

    • Great for performance and filterability.

    • Needs explicit update logic when source changes (if you care about strict consistency).

  • Dynamic join (e.g., BFF**ViewAggregate****)**:

    • Join happens at read time.

    • Always up-to-date but more expensive and requires join context.

In Mindbricks, you often use:

  • Static join for core denormalized fields that are referenced everywhere.

  • BFF/DataViews for rich, multi-object read models.


3. Pattern Structure

A property opts into static-join sourcing by setting basicSettings.source: 'joined' and configuring the flat staticJoin block:

{
  "basicSettings": {
    "name": "clientId",
    "type": "ID",
    "source": "joined"
  },
  "staticJoin": {
    "jointSource": "user",
    "jointRead": "clientId",
    "jointSourceKey": "id",
    "jointForeignKey": "userId",
    "defaultValue": null
  }
}

3.1 Fields Explained

  • basicSettings.source: 'joined'

    • Declares that this property's value is sourced by a static join. The runtime drops the property from input parameters (the client cannot supply it) and fills it from the configured join.
  • staticJoin.jointSource (DataObjectName)

    • The source object to join from.

    • Can be local or remote, e.g. "user", "auth:user", "billing:customer".

  • staticJoin.jointRead (PropRefer)

    • The property in the source object whose value you want to copy.

    • For example, "clientId", "tenantId", "statusName".

    • If not specified, it defaults to using the same property name as the target property (via UI loadfrom hints).

  • staticJoin.jointSourceKey (PropRefer)

    • The key in the source object used to locate the record.

    • Typically "id" or any other unique key.

    • Usually id is the primary field of the source object.

  • staticJoin.jointForeignKey (PropRefer)

    • The field in the current object that refers to jointSourceKey.

    • For example, if the current object has userId referencing user.id, then:

      • jointForeignKey = "userId"

      • jointSourceKey = "id"

  • staticJoin.defaultValue (MScript, optional)

    • Fallback MScript expression used when the join cannot find a source record, or when the joined read value resolves to null / undefined.

These fields tell Mindbricks how to navigate from the current record to the source object and which value to copy.


4. Execution Lifecycle & customData Integration

4.1 Order of Operations in a Create/Update API

For a given property:

  1. Static join runs first (when source: 'joined').

    • Mindbricks:

      • Reads jointForeignKey (e.g. userId).

      • Uses jointSource & jointSourceKey to fetch the related record.

      • Extracts jointRead value (e.g. clientId).

      • Writes that value into the property's field, and exposes the full fetched record under a join-named context key (e.g. this.user).

  2. Per-API customData entries run afterwards for any property with source: 'customData'.

    • These MScript expressions can access:

      • The property itself (now filled with the static join value).

      • The fetched object via this.<jointSource> (e.g. this.user.tenantId).

This layered design lets you chain logic:

  • Use static join to fetch clientId from user.

  • Use formula to compute something else using that clientId.


5. Configuration Examples

5.1 Example 1 – Propagating Tenant/Client ID

Goal: When creating a new Order, automatically copy the clientId from the User so each order has a clientId field.

  • Resource object: order (current DataObject)

  • Source object: auth:user

  • Foreign key in**order****:** userIduser.id

  • Property to populate in**order****:** clientId

DataProperty snippet for clientId:

{
  "basicSettings": {
    "name": "clientId",
    "type": "ID",
    "source": "joined",
    "isRequired": true,
    "allowUpdate": false
  },
  "staticJoin": {
    "jointSource": "auth:user",
    "jointRead": "clientId",
    "jointSourceKey": "id",
    "jointForeignKey": "userId"
  }
}

Result:

  • When order is created/updated:

    • Mindbricks fetches auth:user where id = order.userId.

    • Reads user.clientId.

    • Stores it in order.clientId.

Now you can:

  • Filter orders by clientId directly, in DB or Elasticsearch.

  • Use BFF DataViews that group orders by clientId without joining users.

5.2 Example 2 – Denormalizing Category Name

Goal: Store the category name on the product for direct search/sort.

  • Current object: product

  • Property: categoryName

  • Source: productCategory

  • Foreign key: product.categoryIdproductCategory.id

{
  "basicSettings": {
    "name": "categoryName",
    "type": "String",
    "source": "joined"
  },
  "staticJoin": {
    "jointSource": "productCategory",
    "jointRead": "name",
    "jointSourceKey": "id",
    "jointForeignKey": "categoryId"
  }
}

Now:

  • Full-text search or filters on categoryName do not require a join.

  • BFF or Business APIs can return the name directly from the product document.

Static join also works with membership objects. Suppose projectMember has a membershipType that you want to store directly on some user-specific resource.

  • Current object: userTask

  • Foreign key: userTask.projectIdproject.id; plus userTask.userId

  • Source: projectMember

  • key pair: projectMember.projectId & projectMember.userId

Here static join can be used only if the membership object can be uniquely identified by the foreign keys (you might need a composite index and a helper API).

For more complex cases, you might prefer a BusinessApi FetchObjectAction instead of static join, but the pattern still gives you a denormalization option if you design membership keys appropriately.


6. UI & Pattern Editing Perspective

6.1 For Human Architects (UI)

In the Data Property editor:

  1. Create or select the property you want to use as the target of the static join.

  2. Open the Static Join section.

  3. In Basic Settings, set Source = joined.

  4. Open the Static Join section and fill in:

    • Source object (jointSource)

    • Source key (jointSourceKey – often id)

    • Foreign key in current object (jointForeignKey – e.g. userId)

    • Source property to read (jointRead)

Hints from __jointRead.loadfrom and __jointForeignKey.loadfrom make this easier by offering available properties from the referenced objects.

6.2 For AI Architects (JSON)

Ensure you set:

  • basicSettings.source = "joined"

  • staticJoin (flat — no configuration wrapper) with valid jointSource, jointSourceKey, jointForeignKey, and jointRead (or let some defaults apply).


7. Best Practices & Pitfalls

7.1 Good Use Cases

  • Tenant/Client ID propagation to multiple entities.

  • Storing user’s main organization or region on child records.

  • Storing stable dictionary names (like category, country) for easier search and reporting.

  • Precomputing simplified routing values (e.g., a resolved region code).

7.2 Avoid Overusing Static Joins

  • If the joined value changes frequently and must always be real-time correct, static join may lead to stale data unless you design an update strategy.

  • For highly dynamic attributes, prefer dynamic joins in BFF DataViews or Business APIs.

7.3 Keep Source Data Stable

Static join works best when jointRead fields:

  • Change infrequently (e.g., clientId is stable; categoryName changes rarely).

  • Or where slight staleness is acceptable.

If required, you can create background tasks or Business APIs to resync static-joined fields when source objects change.

7.4 Watch Out for Circular Dependencies

Do not create a static join that:

  • Depends on an object that also depends on the current object in a conflicting manner.

  • For example, Order static-joining from an object that is itself static-joining from Order in a mutually recursive way.


8. Static Join vs Other Join Mechanisms in Mindbricks

MechanismWhere it livesWhen it executesGood for
PropertyStaticJoinDataPropertyCreate/Update timeDenormalization & materialized fields
ViewAggregate (DataView)BFF ServiceRead time (BFF queries)Rich read models across many services
FetchObjectActionBusiness API workflowPer API call, as neededCustom, imperative joins inside workflows

Static join complements these options:

  • Use it for fundamental fields that should exist directly on the object.

  • Use BFF & Business API actions for projection-level or one-off joins.


9. Summary

PropertyStaticJoin lets you define materialized joins at the property level:

  • You configure once:

    • which source object to join (jointSource)

    • which key fields to link (jointSourceKey, jointForeignKey)

    • which value to copy (jointRead)

  • Mindbricks:

    • Automatically fetches the joined value during create/update.

    • Writes the joined value directly to the property.

    • Ensures static joins run before property formulas, so formulas can use the joined data.

This pattern is:

  • Simple to configure

  • Powerful for performance and reporting

  • Great for multi-tenant propagation, dictionary labels, and read-optimized schemas

  • Fully integrated with existing Mindbricks patterns and BFF/DataView architecture

Used thoughtfully, static joins dramatically reduce the need for runtime joins while keeping your patterns clean, declarative, and easy to maintain.