Local Users Cache Documentation
This code implements a local in-memory cache for user profiles and their schedules to improve performance by reducing database queries.
LocalUsers struct
type LocalUsers struct {
wg sync.WaitGroup
mtx SafeMutex
UserProfiles map[string]entity.UserProfileTable // key = user_profile_id
UserProfilesRead bool
UserProfilesByCustomID map[string]*entity.UserProfileTable
UserProfilesByCustomIDMapped bool
UserSchedulesOld map[string]entity.EmployeeSchedules
UserSchedules []entity.UserSchedulesTable
UsersRead bool
CompanyID string
dao *daos.Dao
}
wg sync.WaitGroup: Used for waiting on asynchronous database operations.mtx SafeMutex: A mutex for thread-safe access to the cache. (Likely provides extra safety checks compared to a standardsync.Mutex.)UserProfiles map[string]entity.UserProfileTable: Stores user profiles, keyed byuser_profile_id.UserProfilesRead bool: Indicates if user profiles have been read from the database.UserProfilesByCustomID map[string]*entity.UserProfileTable: Stores pointers to user profiles keyed by custom ID.UserProfilesByCustomIDMapped bool: A flag indicating if theUserProfilesByCustomIDmap has been populated.UserSchedulesOld map[string]entity.EmployeeSchedules: Stores older user schedule data (the structure ofentity.EmployeeSchedulesis not shown in this code snippet, but it likely contains a mapping of user IDs to their schedules).UserSchedules []entity.UserSchedulesTable: Stores the latest user schedule data.UsersRead bool: Indicates if user data (both profiles and schedules) has been read.CompanyID string: The ID of the company for which data is cached.dao *daos.Dao: The PocketBase DAO for database interactions.
globalLocalUsers variable
var globalLocalUsers = LocalUsers{}
A global instance of the LocalUsers struct, indicating a singleton pattern.
Functions
NewUsersCache(dao *daos.Dao, companyID string, read bool, scheduleCache *LocalSchedule) *LocalUsers
Creates a new LocalUsers instance.
dao: The PocketBase DAO.companyID: The company ID.read: A boolean flag; if true, immediately reads user data from the database.scheduleCache: An optional pointer to aLocalSchedulecache. If provided, it's used; otherwise, the global schedule cache is used.
GetGlobalLocalUsers() *LocalUsers
Returns a pointer to the global LocalUsers instance.
SetDao(dao *daos.Dao) *LocalUsers
Sets the DAO for the LocalUsers instance.
GetDao() *daos.Dao
Returns the DAO of the instance.
WaitFinishReadDB()
Waits for asynchronous database operations to finish.
ResetUserProfiles()
Clears the user profiles cache.
ResetUserSchedules()
Clears the user schedules cache.
FindUserByCustomID(customID string) (*entity.UserProfileTable, error)
Finds a user profile by its custom ID. Returns an error if not found. If the custom ID map isn't built, it builds it first.
MapUserProfilesByCustomID()
Creates a map of user profiles keyed by their custom ID, if it hasn't already been created.
ReadAgain(companyID string, force bool, scheduleCache *LocalSchedule) *LocalUsers
Reads user data from the database again. This is triggered if force is true, if the user data hasn't been read yet, or if the companyID has changed.
ReadAllUserProfiles(companyID string, scheduleCache *LocalSchedule)
Reads all user profiles and their schedules for a given company from the database and populates the cache. It uses the provided scheduleCache or the global one if none is provided. It also updates the UserProfiles with schedule information.
FindUserScheduleByUserID(companyID, userID string, scheduleCache *LocalSchedule) []*entity.ScheduleTable
Finds the schedules for a given user.
FindUserProfilesByCompanyID(companyID string, scheduleCache *LocalSchedule) []*entity.UserProfileTable
Finds all user profiles for a given company.
Example Usage
dao := // ... obtain your PocketBase DAO
companyID := "your_company_id"
// Create a new UsersCache and read data immediately
usersCache := caching.NewUsersCache(dao, companyID, true, nil) // nil uses global schedule cache
// Find a user by custom ID
user, err := usersCache.FindUserByCustomID("custom_id_123")
if err != nil {
// Handle error
} else {
// Access user details: user.Name, user.Email, etc.
}
// Get user schedules
schedules := usersCache.FindUserScheduleByUserID(companyID, "user_id_456", nil)
// Get user profiles by company ID
profiles := usersCache.FindUserProfilesByCompanyID(companyID, nil)
// Force a cache refresh
usersCache.ReadAgain(companyID, true, nil)
Improvements and Considerations
- Error Handling:
ReadAllUserProfilescould return errors instead of just logging them. This would improve error handling in calling functions. - Cache Invalidation: The cache lacks an automatic invalidation strategy. Consider implementing time-based or event-driven invalidation.
- Singleton Pattern: Evaluate if the singleton pattern (
globalLocalUsers) is the best approach. Dependency injection might offer better testability and flexibility. - Clarity on
UserSchedulesOld: The purpose and usage ofUserSchedulesOldare not entirely clear and should be documented more explicitly. Consider renaming it or refactoring if it's redundant. SafeMutex: Clarify the additional safety mechanisms ofSafeMutexcompared to a standard mutex.- Locking Granularity: Consider finer-grained locking. For example, separate mutexes for user profiles and user schedules could improve concurrency.
This improved documentation provides more context and details about the LocalUsers cache. By understanding its components and functions, developers can leverage it efficiently and make informed decisions about its usage and maintenance. Remember to address the suggested improvements for a more robust and scalable implementation.