On this page

Shared types

Shared definitions make modules agree on business identity and published contracts. They do not transfer ownership: another module can refer to a User, but Identity still controls account operations and writes.

Choose a type's home#

Type Home Example
Business entity and invariants internal/modules/shared/entities User, Session
Business enum internal/modules/shared/enums User status, authentication platform
Polymorphic type constant internal/modules/shared/entities/entity_types.go UserEntityType
Published event and occurrence metadata internal/modules/shared/events UserRegistered, Event
Service input/result Owning module's service UpdateProfileInput, SignInResult
Repository projection Owning module's repository A query-specific result
HTTP request/response Owning delivery/http Public account response
Capability record/input/error Owning platform package media.Asset, notification.Record
Authenticated caller internal/platform/auth Principal with user and session IDs
Operation error Owning domain/capability Identity authentication failure

Do not move every reusable struct into shared. media.Owner and notification.Recipient have different meanings even though both identify a business model with a type and ID.

Records and transport shapes#

Business entities may carry Bun mappings when the persisted and business shapes agree. This avoids an otherwise identical model plus a conversion layer. Add a separate private query shape only when persistence actually differs.

HTTP responses select public fields deliberately. A Bun entity's JSON tags do not prove that every field is appropriate for every caller. Service inputs likewise exclude transport concerns such as HTTP headers and Huma validation tags.

Migrations change schemas; struct tags do not execute DDL. The owning module registers its migration source and the operator applies it explicitly.

Identity and references#

Use shared entity-type constants at business call sites:

owner := media.Owner{
    Type: string(entities.UserEntityType),
    ID:   user.ID.String(),
}
recipient := notification.Recipient{
    Type: string(entities.UserEntityType),
    ID:   user.ID.String(),
}

This reference fragment assumes an existing *entities.User and imports internal/modules/shared/entities, internal/platform/media and internal/platform/notification under the github.com/wisalgroup/labena module path.

The business caller checks existence and authorization. A (type, id) value is not permission, an automatic model lookup, or an ordinary foreign key across arbitrary tables. The caller also owns deletion policy for attached records.

Type names match [a-z][a-z0-9_.-]{0,63}. IDs are nonempty UTF-8 strings of at most 200 bytes, with no surrounding whitespace or control characters. Media and Notifications share private syntax validation while keeping their public types and errors separate.

Publish a business contract#

internal/modules/shared/events/user_registered.go defines Identity's account-created contract. Constructing an event only describes an occurrence:

event := events.NewUserRegistered(user.ID)

This fragment imports github.com/wisalgroup/labena/internal/modules/shared/events; it does not publish. The event contains UserID and metadata (ID, Type, OccurredAt). Identity owns the meaning and the versioned topic. Consumers own handlers in their own delivery/events package.

Keep contracts limited to IDs and event-specific values. Do not embed complete entities or import repositories, services, application config or transports. Publication and delivery are explicit operations covered in events.

Keep errors with their behavior#

Use pkg/apperror for common classification, safe public codes and validation issues. Define the actual failure beside the module, entity or capability that owns its meaning. There is no global internal/modules/shared/errors catalog.

Across a future service boundary, share serialized schemas and stable error codes. Go interface identity, database mappings and error values are not a transport contract. Extract the contracts needed by consumers rather than exporting the entire shared directory.

Common helpers#

shared/domain.Entity supplies UUID identity and timestamps. shared/domain/valueobjects.Email owns validated email representation and its JSON/database conversion. events.Event is plain occurrence metadata; it neither buffers nor publishes events.

Technical helpers belong in pkg and must not import shared application types. For example, pagination.Pagination remains a reusable paging input; HTTP envelopes belong to internal/platform/api/response.

Continue with conventions, events, and entities and queries.

Documentation

Search guides, sections, and API names.