6 min read

Mastering Model

In the digital tapestry of web apps, models are the threads that weave functionality into form. They organize data, simplify databases, protect integrity, and define APIs. Let's delve into their role in shaping the backbone of web applications.
Colorful crystal cubes, a blend of models to be instantiated and instances already shimmering

This will be a simple delivery to digest, basically, we want to define the model of our application in a way that is useful for our purpose but at the same time generalizable to other applications so that we can cover concepts common to any development we undertake.

With this understanding in place, let's go into the specific components that form the backbone of our application; we'll explore the following key areas:

  • Multi-Tenancy: Tailoring our app to offer each organization its own distinct space, akin to white-label solutions. This approach allows for customization under specific domains, subdomains, or paths, providing a personalized platform for each tenant to operate independently.
  • Multiple Users: Revamping Plykan to accommodate multiple users within each tenant, promoting collaboration and effective project management.
  • User/Account Splitting: Establishing a clear separation between user accounts, enhancing security and efficient user management.
  • Profiles: While not covered in detail in this chapter, we'll lay the groundwork for user profiles, allowing users to personalize their Plykan experience in future chapters.
  • Simple RBAC Implementation: Defining the structure for Role-Based Access Control (RBAC) to manage access permissions within Plykan, ensuring a secure and customized user experience.

The Model

Plykan UML model chart
https://github.com/solutioncrafting/plykan/tree/feature/models/docs/models

Tenant

In the architecture of our system, tenants stand as independent and self-governing spaces operating within their own distinct spheres.

Every independent unit within our system is established and overseen by a designated user who holds the highest level of administrative authority. This individual bears the responsibility for not only setting up the unit but also managing its key decisions, including the authority to potentially remove it if desired.

User, Account, and Affiliation

User

This entity is at the core of our system, representing individuals who engage with the application. While Users are essential, they do not directly connect with tenants. Their interaction with each tenant is mediated through another component of our system, the Account.

Account

This component is essential for a User's interaction with a tenant. Each Account is deliberately associated with a Tenant, establishing the foundation for user interaction. While the system is designed to accommodate future enhancements, the primary function of an Account as the gateway for Users to access and engage with the specific functionalities and resources of a Tenant remains a consistent aspect of our model.

Affiliation

Affiliation acts as the bridge between an Account and a Tenant. It's the mechanism that links a User's Account to a Tenant, delineating the scope and nature of the User's involvement within that Tenant.

Within the context of our Kanban board application, the relationship type between Accounts and Tenants is left unlabeled. This approach is sufficient for our present needs. However, we've intentionally designed the model with the capability to specify affiliation types in the future if necessary.

Roles, Permissions, and Resources

Each Account is assigned specific Roles, which come with associated Permissions. These Permissions are critical in determining the activities an Account can engage in within a tenant.

The RBAC implementation also incorporates the flexibility of direct Permission assignments to Accounts. This dual approach allows for more nuanced access control, enabling Accounts to have a unique set of capabilities beyond their Role-defined boundaries.

In managing Resources, our system assigns each a unique identifier, and possibly a tag, for ease of management and location. In our system, Resources play a key role, each demanding specific Permissions for access and functionality. In the context of our application, business entities like Boards are treated as such Resources. They are the actual entities we aim to protect and manage access to, embodying the tangible aspects of our system's functionality.

Multitenancy Approach

In multi-tenant architecture, there are diverse approaches to organizing and manage data across different organizational units. These include allocating a unique database for each unit, employing individual schemas within a shared database for every unit, and adopting a column-based approach where a single table stores all data, differentiated by columns specific to each organizational unit.

For our application, we have chosen to implement the column-based tenancy approach. This method involves storing data for all tenants in shared tables, with each record including a reference to the Tenant. This tenant reference forms part of the key used to locate and segregate data. By incorporating the Tenant ID in our data structure, we can efficiently identify and access tenant-specific data within our shared tables.

This approach allows us to maintain a streamlined database architecture while ensuring that data is securely segregated and easily accessible on a per-tenant basis. Additionally, in the event that a tenant owner decides to cancel their tenant, this structure facilitates a rapid and accurate process for locating and removing all entities associated with it.

Go Implementation

As we delve into explaining the Go implementation, it's important to note that the actual code, being straightforward yet somewhat repetitive for each model, is best followed directly in our GitHub repository.

We have structured our Go models to reflect the relationships and functionalities necessary for a robust multi-tenant board system. These models are located in the internal/model package, with foundational elements derived from internal/sys/model. Let's briefly discuss the general structure and purpose of these models.

Tenant and its Profile

Tenant

This model encapsulates the concept of a tenant within the system. It includes essential fields like Name, FantasyName, URL, andOwner, with the Owner field linking to the Account model. The Email field serves as a technical contact point and Active indicates the tenant's operational status. The model incorporates the ID and Audit structs, providing unique identification and traceability of changes.

TenantProfile

Currently, this model isn't the focus, but it's structured to potentially hold detailed profiles for each tenant.

User and UserAuth

User

This model represents a system user and includes comprehensive fields like Username, PasswordDigest, and Email. It features authentication and security-related fields like ConfirmationToken and IsConfirmed. It also has LastIP and LastGeolocation, the latter using the GeoPoint structure from the core model for precise location data. The TenantIDs field holds references to the tenants the user is associated with as in other models.

UserAuth

An extension of the User model, will be used for enhanced authentication mechanisms.

Account and its Profile

Account

The Account model signifies a user's presence within a specific tenant and includes key information such as Name and Description. While the Name field also exists in the User struct, its presence in Account serves a distinct purpose. It allows users to choose how they wish to be publicly represented in different contexts, which can be particularly useful when a user holds accounts with varied roles across multiple tenants. This flexibility extends to other properties as well, like the avatar image, enabling users to tailor their public persona and profile elements to suit each specific role or tenant environment.

AccountProfile

Designed for storing non-critical account data, it allows for richer user profiles within each tenant.

Affiliation

This model establishes the connection between a User and a Tenant. It primarily consists of the IDs of both entities, providing a link between a user and the tenant they are affiliated with. We can also store additional information about the nature of the affiliation. This could include specifying the type of role or relationship the user has with the tenant, such as being an employee, contractor, or a general user. Although this feature will not currently be in use in our application in an initial stage, the model is designed to accommodate such details in the future.

RBAC

Role, Permission, and Resource

These models are foundational to the RBAC system, defining roles, permissions, and resources within the application. Each RBAC model instance will tied to a specific tenant and also include fields for name, description, and audit trails.

Board, Column, Task

Board

Represents a Kanban board, with fields for name, description, and columns. It's linked to a Account (as owner) under a specific Tenant.

Column and Task

Integral to the Kanban board, the Column model holds a collection of Tasks, each characterized by attributes such as name, description, and status. Looking ahead to deeper implementation aspects of our application, we plan to introduce methods specifically for retrieving tasks based on their status, which is intrinsically tied to their column association.

What's Next

As we continue to evolve Plykan, our next focus will be on the foundational aspects of Domain Driven Design (DDD), particularly the concept of Ports, more specifically, driven ports. By defining interfaces based on these driven ports, we will establish a standardized method for model access.

Building upon these interface definitions, we will delve into standardized data access by exploring the repository design pattern. This phase marks the intersection of theory and practice, as we implement the defined interfaces in a manner specifically suited to our chosen database, PostgreSQL. Moreover, we'll also demonstrate how the repository pattern enables us to effortlessly create implementations for various other persistence mechanisms, whether they are relational or not. By abstracting away the finer details of persistence, the repository pattern will allow us to focus on the broader aspects of data management, ensuring flexibility and adaptability in how our application interacts with different data storage systems.

To accomplish this, we will introduce first the Go database/sql lib. This standard library in Go provides a generic interface for interacting with relational databases, making it a handy tool in our development arsenal. Alongside it, we'll also explore sqlx, an extension package that enhances the capabilities of the stdlib version. sqlx offers additional functionality and convenience, making database interactions more intuitive while maintaining compatibility with the standard library.

Resources