Attribute Modeling
Use this guide when designing a transcribe configuration or reviewing a slow relationship search. The goal is to store the source data where it changes naturally and expose derived views only where clients need them.
Decision matrix
Section titled “Decision matrix”| Question | Store a plain attribute when... | Use a virtual attribute when... |
|---|---|---|
| Where does the source system own the value? | The source LDAP entry already carries the value and updates it directly. | The value is a reverse lookup or a derived view of another entry's data. |
| How many values sit on one entry? | Each entry has a small, bounded set of values. | Storing the value would create large repeated lists on many entries. |
| How do callers filter? | Callers often filter or page by that exact attribute. | Callers usually request the value after selecting a page of entries, or filter by selective relationship values. |
| How often does it change? | The value changes with the entry and should be captured in change history. | The value changes because related entries changed. |
| Does another system write it? | Upstream applications expect to modify that attribute on the entry. | The attribute should be read-only in Scribe and computed from stored data. |
| Is the reverse direction needed? | Store the direction with the smaller per-entry value set. | Expose the reverse direction as a virtual attribute for convenience. |
If both sides look reasonable, start with the side that has fewer values per entry. This keeps sync writes smaller, keeps index coverage focused, and avoids storing the same relationship many times.
Relationship rule
Section titled “Relationship rule”For many-to-many relationships, choose the stored side by fanout:
| Relationship | Store this side | Expose this virtual side |
|---|---|---|
| Users and groups | Store group membership on the user when each user has tens of groups. | Expose group members on the group side only when clients need that reverse view. |
| Users and roles | Store assigned roles on the user when role sets are small. | Expose users carrying a role as virtual when role pages or audits need it. |
| Managers and reports | Store manager on the employee entry. | Expose directReports as virtual on the manager entry. |
| Owners and resources | Store owner or owners on the resource when each resource has few owners. | Expose ownedResources as virtual on the owner entry. |
A group can contain thousands of users, while one user usually belongs to tens or hundreds of groups. In that shape,
store the user's group list if the source directory can provide it. If the source only stores member on group entries,
sync member on the group transcribe and expose memberOf on users as a virtual attribute.
Plain attribute pattern
Section titled “Plain attribute pattern”Use plain attributes for values that belong to the entry and are queried directly:
transcribes { users { ldap { base = "ou=users,dc=example,dc=com" filter = "(objectClass=person)" attributes = "cn, mail, departmentNumber, manager, assignedRole" } indices { value-match = "mail, departmentNumber, manager, assignedRole" sortable = "cn, mail" } }}This pattern is best for identity fields, status fields, manager links, owner links, role lists, and other values that clients filter by directly.
Virtual attribute pattern
Section titled “Virtual attribute pattern”Use virtual attributes for a reverse view over stored data:
ldap.virtual-attributes { directReports { types = ["users"] filter = "(&(entryType=users)(manager={{self.entryDN}}))" value = "{{other.entryDN}}" }}The stored attribute remains manager on each user. directReports is computed when a caller asks for it, so managers
do not store large report lists and updates follow the employee record.
Group and role membership
Section titled “Group and role membership”Membership attributes deserve a deliberate choice because they can grow quickly.
Use this checklist:
| If... | Model it as... |
|---|---|
| The source can provide each user's short group or role list | Store that list on the user and expose group members or role holders as virtual reverse views when needed. |
| The source only owns a group's long member list | Store member on the group and expose memberOf on users as virtual. |
| Callers mostly ask "which groups is this user in?" | Prefer the user-side attribute or a user-side virtual memberOf. |
| Callers mostly ask "which users are in this group?" | Query the stored group member attribute or expose a group-side virtual view only for selected pages. |
| Membership lists are used for authorization | Prefer the canonical source-owned side and keep the reverse side read-only. |
For very large directories, avoid storing both directions unless both are source-owned and required by external consumers. Duplicate relationship storage makes reconciliation and change review harder.
For eDirectory Identity Manager role assignments, use the eDirectory Identity Manager Roles profile. It stores user-side role and group membership values, then exposes role and group reverse views as virtual attributes.
Indexing after the choice
Section titled “Indexing after the choice”After choosing the stored side, add only the coverage callers use:
| Caller behavior | Coverage |
|---|---|
Exact filters such as assignedRole = "admin" | value-match |
| Starts-with filters | value-match |
| Contains or fuzzy filters | partial-match only when callers issue those searches |
| Sorted pages | sortable for the sorted attribute |
Virtual attributes still depend on the stored attributes behind them. Give the stored relationship attribute the coverage needed by the virtual lookup, then verify search behavior in Observe.
When to revisit
Section titled “When to revisit”Revisit the model when:
- one side of the relationship grows far beyond the original estimate;
- callers start filtering or sorting by a virtual attribute on high-traffic paths;
- change history needs to show the derived value as if it were stored;
- the source directory changes which side owns the relationship.
Changing the model later is possible, but it is a schema decision. Treat it like adding a new transcribe attribute: plan the index coverage, run the initial sync, and verify query behavior before depending on it in applications.