Building Power Apps solutions templates - part 1

Most people working with the Microsoft Power Platform and Dataverse probably know what the default Common Data Model is. When working with many different organizations having distributed business units or their company branches in many countries, I often encounter the requirement to create a Power Platform application template, which could be created once, then used and successively customized in many separate Dataverse environments. And mentioned “templates” should contain not only data models but also building blocks of customized or newly built functionalities. Sometimes it is named “global template" and sometimes “common template," but the idea is basically every time the same.
I hope this text is the first one from the series of articles about best practices of Power Platform-based application templates. The idea is to share some thoughts about best practices regarding the mentioned topic and also describe some common mistakes we should avoid. Today I would like to write about working with a “common data model” and how to work with Dataverse solutions to avoid problems during their deployment into target environments.
Our first idea was simple. We’d like to split the tables' definition into separate solutions. The first base-solution would contain only data structure definitions (which are tables with their columns), and all the business logic and front-end components should be in separate ones, different for separate target environments. Our system was based on highly customized Dynamics 365 Sales applications. You can find the visualization of this idea below:
Everything was working fine with standard tables. We were able to add custom columns for these in the base solution and custom UI elements inside dedicated solutions (marked with capital letters on the picture above). The problems started with custom tables. We have tried to create these in the base solution. As you probably know, all the default components like forms and views are also automatically added to the solution where the table is created. And it is not possible to remove them. “Ok. We can live with it," I thought. “We will be using custom forms and views in our apps nevertheless." Unfortunately, all the custom UI components were also automatically added to the solution where the table had been created, which was the “base” one in our case. It is the default behavior of parent solutions for tables or for solutions where a table is added with the “Include all objects” parameter. Unfortunately, it was also something very undesirable for us.
The next idea was to use a “temporary” solution for table creation. Then add the default definition of the created tables to the base one. That’s how we could avoid all the custom forms and views being automatically added to the base solution. Unfortunately, it occurs that it is impossible to import such a base solution into the target environment because of the missing dependencies. We were getting, somehow, a funny error, which was:
It is not possible to import Demo Table because it is dependent on Demo Table? Makes sense? Let’s do a little deep dive and take a look at the unpacked solutions.
I have unpacked my “temporary” solution where the table had been created and opened the solution.xml file. There were no surprises. The part of the file I was mostly interested in looked in the following way:
<RootComponents>
<RootComponent type="1" schemaName="pg_demotable" behavior="0" />
</RootComponents>
<MissingDependencies />
In case we’ll take a look at the same section in the solution, where a table has been added without the “Include all object” parameter, we may observe some differences:
<RootComponents>/>
<RootComponent type="1" schemaName="pg_demotable" behavior="1" />
</RootComponents>
<MissingDependencies>
<MissingDependency>
<Required type="1" schemaName="pg_demotable" displayName="Demo Table" solution="Active" />
<Dependent type="1" schemaName="pg_demotable" displayName="Demo Table" />
</MissingDependency>
</MissingDependencies>
The most important difference is value of “behavior” attribute. According to official documentation, it means that root component’s subcomponents aren’t automatically added to the solution. However, I was not able to find any information that value other than “0” blocks us from importing component into target environment, even in case all the required subcomponents were manually added to the solution.
How may we solve this issue? The first option is just admitting that Dataverse's data model template is not only tables and attributes but also forms, views, dashboards, and other components (like front-end JavaScript libraries). This is not a highly desirable solution from the “classic” software engineering perspective. On the other hand, it is consistent with the “model-driven app” definition, where UI components are tightly coupled and dependent on the data model. We can modify our architecture diagram for this pattern in the following way:
There is also another option. We may split the Dataverse development environment into 2 separate ones. The first one should contain the base solution and should be only for table creation. And by “table” I mean a table with all the default forms, views, etc. Such a solution may be used as a deployment artifact and may be used as a “base” for dedicated "application" solutions, which may contain custom UI components without breaking any dependency rules.
Having 2 development environments makes the development process (visualized on the picture below) longer. For example, in the case of a situation where we need to create a new column and add it into a custom form, we need to add it into the first environment (“base”), export and import its solution into the second environment (“app development”), and after that add it into the custom form there. However, I suppose this is an acceptable situation in a highly controlled enterprise environment.
And remember that we still do not eliminate all the UI components from the base solution because default components are still there. However, I suppose we need to live with it in our lovely low-code applications world😉.



