Best practices to store user information/settings in Caché

I'm interested in different approaches on how to store user data in Caché. I'm assuming that application uses Caché security/Caché users and not a self-made authentication system.

Several approaches, I'm familiar with:

1. Store data in Security.Users:Attributes property. Sample methods:

/// Set settings for User equal to Settings
ClassMethod SetSettings(Settings As %String = "", User As %String = {$Username}) As %Status
{
	New $Namespace
	Set $Namespace = "%SYS"
	Set Properties("Attributes", "Settings") = $LB(Config)
	Set st = ##class(Security.Users).Modify(User, .Properties)
	Return st
}

/// Get settings for User
ClassMethod GetSettings(User As %String = {$Username}) As %String
{
	New $Namespace
	Set $Namespace = "%SYS"
	Set st = ##class(Security.Users).Get(User, .Properties)
	Return $ListGet($Get(Properties("Attributes", "Settings")))
}

Advantages:

  • Easy to implement
  • Easy to retrieve some system information about user

Disadvantages:

  • Requires resources 
    %DB_CACHESYS:RW, %Admin_Secure:U

2. Reference Security.Users as a property from your own class. Sample class:

Class Utils.UserInfo Extends %Persistent
{

Property User As Security.Users;

Property Settings As %String;

Index UserIndex On User [ IdKey, PrimaryKey, Unique ];

/// Set settings for User equal to Settings
ClassMethod SetSettings(Settings As %String = "", User As %String = {$Username}) As %Status
{
	If ..%ExistsId(User) {
		Set settingsObj = ..%OpenId(User)
	} Else {
		Set settingsObj = ..%New()
		Set userObj = ##class(Security.Users).%OpenId(User)
		Set settingsObj.User = userObj
	}
	Set settingsObj.Settings = Settings
	Return settingsObj.%Save()
}

/// Get settings for User
ClassMethod GetSettings(User As %String = {$Username}) As %String
{
	Return ..SettingsGetStored(User)
}
}

Advantages:

  • Does not need to open an object to retrieve settings

Disadvantages:

  • Requires mapping of Security.Users class  to an application namespace

3. Reference User as a string property. About the same as the previous option. Sample class:

Class Utils.UserInfo Extends %Persistent
{

Property User As %String;

Property Settings As %String;

Index UserIndex On User [ IdKey, PrimaryKey, Unique ];

/// Set settings for User equal to Settings
ClassMethod SetSettings(Settings As %String = "", User As %String = {$Username}) As %Status
{
	
	If ..%ExistsId(User) {
		Set settingsObj = ..%OpenId(User)
	} Else {
		Set settingsObj = ..%New()
		Set settingsObj.User = User
	}
	Set settingsObj.Settings = Settings
	Return settingsObj.%Save()
}

/// Get settings for User
ClassMethod GetSettings(User As %String = {$Username}) As %String
{
	Return ..SettingsGetStored(User)
}
}

Advantages:

  • Does not need to open an object to retrieve settings

Disadvantages:

  • Invalid data may be entered, the check that username is correct would again require some form of access to Security.Users

4. Store settings in global.  Sample methods:

/// Set settings for User equal to Settings
ClassMethod SetConfig(Config As %String = "", User As %String = {$Username}) As %Status
{
	Set ^SettingsGlobal(User) = Config
	Return $$$OK
}

/// Get settings for User
ClassMethod GetConfig(App As %String, User As %String = {$Username}) As %String
{
	Return $Get(^SettingsGlobal(User))
}

Advantages:

  • Easy to set up
  • Does not need to open an object to set or  retrieve settings

Disadvantages:

  • Invalid data may be entered, the check that username is correct would again require some form of access to Security.Users
  • No object or SQL access

 

So, are there any other solutions? Moreover, what is the best approach?

  • + 1
  • 1
  • 689
  • 2
  • 0

Comments

Personally, I would not go for 4) and 1).

4) Allows invalid data to be entered and a class definition helps you to query users based on user data later on. This approach just limits your options in the future.

1) I don't like this approach just because it does not separate the concerns and it requires privileges.

2) and 3) are both good approaches. 2) Includes a sanity check because of the reference, but you have to be aware of this if you want to move user data between servers in case the users are not in sync.

I guess you are asking for ways to persist user data, but just in case: If you are building a CSP/Zen/Zen Mojo application you can store session data for a user in %session.Data. As soon as a transaction is complete and you want to store it you can go with approach 2) and 3) and persist the relevant data in your own class structure. 

Thank you Stefan!

Yes, I'm asking about persistent data, of course.