Read-only entities with multi-tenant applications, help :-/

  

How do you have read-only entities while using multi-tenant applications if in one module you need to expose the Tenant ids in order to perform queries across Tenants, but also need to update the data in certain places?


Explanation:

We have wanted to go to read-only entities for our schema for numerous reasons.  I take these following steps:

1. Change "Expose Read Only" to Yes on our multi-tenant entities:

2. Create wrapper functions for each entity, like so:

3. Our main application doesn't want to expose the Tenant Ids, i.e. security, management, etc.

4. We have another module that is a business management application for the main application.  It works with all the Tenants, and we need the ability to query across those Tenants to get a list of records.  So in that app we reference the entity and turn on "Show Tenant Identifier":

5. This is all fine if you don't want to modify any records, but we do.  So we reference those wrapper functions from the main application, i.e. CreateOrUpdate_Customer.  Once I do that I get the warning from the main module of:

Not sure if you can see that, but it's the "Invalid Reference" warning of "Action Foo from MyModule cannot be referenced. It uses entity 'Customer' that has the property 'Show Tenant Identifier' with a different value from the original entity definition."

This totally makes sense, so how do I go about handling this situation?

I want to have read-only multi-tenant entities with hidden tenant identifiers and another application to show the tenant identifiers to also work with the data.  How?


Thanks for any help guys!

Hy Shawn,

Have you considered having a tructure with the same attributes of the entity for the update action that you want public?

It should solve the problem you are facing. You need to convert between the structure and the entity in a couple of places, but that should be simple.

Hope it helps 



Shawn Hall wrote:

How do you have read-only entities while using multi-tenant applications if in one module you need to expose the Tenant ids in order to perform queries across Tenants, but also need to update the data in certain places?


Explanation:

We have wanted to go to read-only entities for our schema for numerous reasons.  I take these following steps:

1. Change "Expose Read Only" to Yes on our multi-tenant entities:

2. Create wrapper functions for each entity, like so:

3. Our main application doesn't want to expose the Tenant Ids, i.e. security, management, etc.

4. We have another module that is a business management application for the main application.  It works with all the Tenants, and we need the ability to query across those Tenants to get a list of records.  So in that app we reference the entity and turn on "Show Tenant Identifier":

5. This is all fine if you don't want to modify any records, but we do.  So we reference those wrapper functions from the main application, i.e. CreateOrUpdate_Customer.  Once I do that I get the warning from the main module of:

Not sure if you can see that, but it's the "Invalid Reference" warning of "Action Foo from MyModule cannot be referenced. It uses entity 'Customer' that has the property 'Show Tenant Identifier' with a different value from the original entity definition."

This totally makes sense, so how do I go about handling this situation?

I want to have read-only multi-tenant entities with hidden tenant identifiers and another application to show the tenant identifiers to also work with the data.  How?


Thanks for any help guys!



Hey Lucio, thanks for the reply!

Yes I have but not as much as I recently have thanks to you....but then any little change to the entity down the road has to be done to the structure.  And I'm not entirely sure (haven't tested) how a field of Tenant_Id would be handled with the structure.  Actually thinking about it now, there probably is no issue with the Tenant_Id.

What would be the best way to do that conversion automagically?  Is there something already existing that takes either/or and converts and matches fields?  I'm sure that can be done in an extension if there is nothing existing that could do that.

From your experience, what disadvantages are there to a structure approach?


The other alternative, which isn't nice either, is actually just sending json data to the CreateOrUpdate and I have that working with a little test.


By the way, I think for any of these solutions to work (unless there is a better solution) I actually need to make my Entities with the Tenant exposed, as opposed to not.  Because since they are read-only, the wrapper functions will need to make any changes to a Tenant Id if necessary.  Whereas in the main application, I'd just hide the Tenant Id's.  Yes?

This is how I have it working in my json test.


Any other thoughts?

The down side of using a structure is that it can get out of sync with the entity. I would still use it instead of a json "blob" as it makes code more clear.

About the tenant_id handling, inside your createorupdate you need to fill that using the site.tenantid value.

Hope it helps.

Shawn Hall wrote:

Hey Lucio, thanks for the reply!

Yes I have but not as much as I recently have thanks to you....but then any little change to the entity down the road has to be done to the structure.  And I'm not entirely sure (haven't tested) how a field of Tenant_Id would be handled with the structure.  Actually thinking about it now, there probably is no issue with the Tenant_Id.

From your experience, what disadvantages are there to a structure approach?


The other alternative, which isn't nice either, is actually just sending json data to the CreateOrUpdate and I have that working with a little test.


Any other thoughts on that?



haha Sorry Lucio, I edited my previous answer as you were responding, I added:


What would be the best way to do that conversion automagically?  Is there something already existing that takes either/or and converts and matches fields?  I'm sure that can be done in an extension if there is nothing existing that could do that.


And from your answer though I'm guessing there is no simple way of doing that than literally doing an assignment.  I may need to look at doing an extension down the road that can do this.


Yes, I do agree how having a structure though is way more legible from a programmer perspective as opposed to serializations.

A plain assign should do it... :)


Yeah, thanks again for your help Lucio!  I really appreciate it.  I'm going to not yet mark yours as a solution yet just to see if anybody else has any ideas within the next week or so.


Have a great December!

Best regards.

Just an FYI, so we've had this structure way working fantastic, and all applications work with the entities exactly as they need.  A large issue that was discovered yesterday from this implementation is now any updates do full-updates because the optimizations don't know how to do partial-updates when you now are doing a conversion from one data-type to another.  The optimizer works as expected and sees every field as being updated so it updates every single field, even if the end-user only changed one field.  :(   


Okay, full explanation time:

1. All of our entities look like this:



2. In one application, our kind of business management app, accesses (reads and modifies) data across tenants.  The only reason why this app needs to do this is because we treat each of our franchises as a different Tenant, and the parent company needs to be able to do certain management tasks across those franchises.  So in this app we have our entity refs as such:


3. Main app has entity refs like so because we want everything to be isolated to a Tenant:


4. Since our entities are read-only, we have helper actions of CreateOrUpdate_Customer that expect a structure as input, like so:


5. The only places we currently make use of doing conversions is in 2 places.  The first is in our other applications where we call this CreateOrUpdate_Customer action, like so:

We handle the Tenant_Id in the action so we don't always have to set it to like Site.TenantId in our main app (not showing TenantId) or we set it to a value in our business app when we work with the TenantIds. The second conversion is inside the CreateOrUpdate_Customer action so it can actually do an update against the DB, like so:


6. Since this does full-updates like this, we have even done a modification like so inside our entity wrapper actions (CreateOrUpdate_Customer) to get partial updates:

Currently in this test setup in that "Assign" assignment it maps every single field.  So since it takes what's in the DB and does an assignment field-by-field, the optimization works again!  The actual SQL update will be a partial-update for field values that differ.  Now, I've read this in other forum posts here, but this is a big problem doing this!  This is the Mary/Sally issue:


Conclusion:

I don't know how to implement things to get what we want.  My original desire was "I want to have read-only multi-tenant entities with hidden tenant identifiers and another application to show the tenant identifiers to also work with the data.  How?"  Now on top of that we want to make use of partial-updates and not lose out on optimizations.  On top of all of this, I need to log our own record change history for many reasons but most important is for system integrations.

What me and my co-worker concluded is that we shouldn't have our franchises in separate Tenants in the first place.  That we should maybe never allow ourselves to have an application to modify data across Tenants, especially since we're going to have other Tenants who are not our own franchises and it could be a big problem if we had our own bugs in places working with those Tenants.  That from an architectural view our franchises should just be a separate location and we do our own filtering,etc where it needs be.


Any thoughts on partial-update optimizations with using structures and going down that path?  (because we may not be able to now change how we handle our franchises at this point)

Hi Shawn,

The optimizer will not work for your scenario as it assumes you are updating all the attributes. 

If you want to do partial updates, you need to explicitly compare what you want to update with what you have in the database. For each different attribute use an assign to change the record you are preparing to update and finally use the update entity action.

Hope it helps

Happy new year


Shawn Hall wrote:

Just an FYI, so we've had this structure way working fantastic, and all applications work with the entities exactly as they need.  A large issue that was discovered yesterday from this implementation is now any updates do full-updates because the optimizations don't know how to do partial-updates when you now are doing a conversion from one data-type to another.  The optimizer works as expected and sees every field as being updated so it updates every single field, even if the end-user only changed one field.  :(   


Okay, full explanation time:

1. All of our entities look like this:



2. In one application, our kind of business management app, accesses (reads and modifies) data across tenants.  The only reason why this app needs to do this is because we treat each of our franchises as a different Tenant, and the parent company needs to be able to do certain management tasks across those franchises.  So in this app we have our entity refs as such:


3. Main app has entity refs like so because we want everything to be isolated to a Tenant:


4. Since our entities are read-only, we have helper actions of CreateOrUpdate_Customer that expect a structure as input, like so:


5. The only places we currently make use of doing conversions is in 2 places.  The first is in our other applications where we call this CreateOrUpdate_Customer action, like so:

We handle the Tenant_Id in the action so we don't always have to set it to like Site.TenantId in our main app (not showing TenantId) or we set it to a value in our business app when we work with the TenantIds. The second conversion is inside the CreateOrUpdate_Customer action so it can actually do an update against the DB, like so:


6. Since this does full-updates like this, we have even done a modification like so inside our entity wrapper actions (CreateOrUpdate_Customer) to get partial updates:

Currently in this test setup in that "Assign" assignment it maps every single field.  So since it takes what's in the DB and does an assignment field-by-field, the optimization works again!  The actual SQL update will be a partial-update for field values that differ.  Now, I've read this in other forum posts here, but this is a big problem doing this!  This is the Mary/Sally issue:


Conclusion:

I don't know how to implement things to get what we want.  My original desire was "I want to have read-only multi-tenant entities with hidden tenant identifiers and another application to show the tenant identifiers to also work with the data.  How?"  Now on top of that we want to make use of partial-updates and not lose out on optimizations.  On top of all of this, I need to log our own record change history for many reasons but most important is for system integrations.

What me and my co-worker concluded is that we shouldn't have our franchises in separate Tenants in the first place.  That we should maybe never allow ourselves to have an application to modify data across Tenants, especially since we're going to have other Tenants who are not our own franchises and it could be a big problem if we had our own bugs in places working with those Tenants.  That from an architectural view our franchises should just be a separate location and we do our own filtering,etc where it needs be.


Any thoughts on partial-update optimizations with using structures and going down that path?  (because we may not be able to now change how we handle our franchises at this point)



Hi Lucio!  Thanks, happy new year to you too :)

Yeah, so what you said is what we're doing in my #6, where we query from the DB and do a full assignment of all fields after that, followed by the entity's own Update.  But doing that gives the Mary/Sally issue....

What happens in this example is that even though Mary did not change Joe's birthday, it is a "change" going from the current data in the DB with hers.  Mary's value will overwrite the actual value in the DB.



Lúcio Ferrão wrote:

Hi Shawn,

The optimizer will not work for your scenario as it assumes you are updating all the attributes. 

If you want to do partial updates, you need to explicitly compare what you want to update with what you have in the database. For each different attribute use an assign to change the record you are preparing to update and finally use the update entity action.

Hope it helps

Happy new year