Don’t worry – you didn’t miss any information regarding Microsoft coming out with a new type of apps 🤪. This is merely a post where I’ll explain a solution that I had to go through, and some thoughts that might be interesting for you as well ;-).
Previous post, I explained about a “booboo” that I did. Let me go a bit deeper into that, and explain why it’s still a path we’d like to pursue .. . After, I’ll explain what we did, how we did it, and why it might be a good thing to do anyway ;-).
Offerings are not “offerings”
When you’re publishing a Business Central offering on AppSource, you need to do quite some work on Partner Center. Personally, I’m not responsible for that (thank God!), but I do have to deliver a few bits ‘n bytes, being: the (what we call) main app, the test app, and a zip with the library apps:
- The Main App: this is the app that represents the offering. The one that holds the name (which needs to be the same as your offering), the App Id … and which might have some dependencies.
- The library apps: this is a zip file with zero, one or multiple apps. You can go nuts with this one – what is important is that you at least include the apps, the entire dependency tree, that you need to be able to install your main app. A missing app down your dependency tree will fail your validation.
- The test app: completely useless part of the entire story, as Microsoft won’t be running any of your tests. You can use an empty app for all they care. Thing is, back in the days, it WAS mandatory. One can only guess why this requirement was removed (not to increase the quality, I guess) – but they were too lazy to fix the upload-site as well (sorry, Microsoft, but it’s simply very unprofessional to leave it like this – I thought we were in the software business…. 🤷♂️🙄) so we still have to upload a fake app. +2200 AppSource apps .. +2200 fake test apps just because somebody is not able to take a textbox offline. Anyway .. not really the topic, is it … (sometimes, the autistic-side of me takes over.. that’s also why we ARE uploading a true test-app 😉).
I have been advocating “dependencies” in the past. I’m completely in favor of using “apps” as an architectural capability: we can use apps as modules, that we reuse in multiple other apps. And that’s what we did, resulting in many kind of different apps:
- “Functionality apps” – apps that actually adds (or changes) functionality. Let’s say, the “normal” apps ;-). Usually encapsulated functionality, separately sellable, .. .
- “Connecting apps” – just in case 2 (functionality) apps are installed, it might be that we want to tweak functionality to make some of the user experience better. These apps usually are only necessary when 2 specific apps are installed, only then, and else, not at all – because nothing has to be tweaked. Typically, it would have dependencies on both. There are some tricks where you could include the code in one of the apps – but then you have to do some generic trics that make it work – included in one of the apps, not both (which one) … it just feels architecturally wrong .. 🤷♂️. These apps usually contain a very limited amount of objects.
- “Library apps” – very much like the system app: libraries to make specific problems easy to solve. We tend to encapsulate these as well – this way, we have a “print app”, a “file app”, and so on.. .
- “Framework Apps” – Very similar to library apps, only with functionality. These apps need a lot of settings, very specific per customer. One example would be something like a “Company Sync”, where we would replicate data from/to companies. Which data, which fields, .. depends on the customers/extensions .. . In some cases, even development is need to be able to use the functionality of these apps.
This has result in quite a collection of apps, where in many cases, the “Functionality Apps” don’t have a dependency to all of them. And I must say – we LOVE it! The encapsulation of code, the modular design .. It really makes the maintenance, the upgrade, the reusability, … very interesting. I would almost say it forces us to do the right thing (we simply can’t take shortcuts), and also do that thing just once .. . No code cloning, simple upgrades, maintainability, readability, encapsulation, testability, .. all quite important concepts in software development, if you ask me 😉.
On top of that, we have been developing everything always with AppSource in mind: Target cloud, registered prefixes, all codecops, .. We comply with about all you can comply with. So .. It was inevitable – at some point:
AppSource, here we come
At least, that’s what we thought. As I explained – Offerings are not “offerings”. Since we have divided our functionality among multiple apps, many of these apps just don’t make any sense to be published by itself to AppSource (especially these low level apps). Those “functionality apps” are the only ones that would make sense, but even with those, we’d like to combine as well (or some of them, at least). And out of the box, that’s simply not possible, because:
- Your offering on MPN needs to have the same name as your “Main App”
- It will only take the dependency from your “Main App” as library apps, there is no way for you to include other apps that have no dependencies to your Main App.
And it’s by design:
And that simply doesn’t work for us!
In my previous post, I also already talked about a possible solution .. and I was VERY pleased to see that the theory worked out. Even more, I’m starting to think this might be a good pattern to implement for every single AppSource offering .. 🤔.
The Offering App
The idea is simple: in stead of using any app with actual functionality or anything .. like we would do instinctively .. create yet ANOTHER app with only dependencies to the apps that you want in your offering. Publish that one as your “main app”, and you can include any app in your offering. Simple, isn’t it.
Remember, you only need the top-level apps in your dependencies – all underlying dependencies will be included automatically!
You might remember from my previous post that I claimed that you needed some kind of coded reference to the actual app to “confirm” the dependency. Well .. I was wrong (and I’m happy I was ;-)).
The trick is to set the “
propagateDependencies” property in the app.json. I didn’t really think about it at first, but then I remembered the so-called “Application”-app from Microsoft, which basically is exactly what I was trying to accomplish here: it’s collecting the system-app and the base-application in one app. It ONLY contains an app.json, and what stood out to me was the “
I knew the property, but I wouldn’t have expected to have any influence in this case, because it’s not meant for this purpose (as far as I know). It kind of like makes sure that THIS app is able to code against apps where the System Application and the Base Application depend on .. 🤷♂️.
But in any case – it was key for getting this to work without any dummy codeunits, unused variables, empty subscribers .. or any similar nonsense.
- create an app with only an app.json,
- add your dependencies,
- set the propagatedDependencies to true,
- and compile it into an app!
(well, to comply with AppSourceCop, you need to still add an IdRange as well 🤷♂️ – go figure).
This extension should be used as your Main App. Just include a zip with all your library apps (the full dependency tree!), and you’re good to go!
Microsoft used the “
propagateDependencies” property only once in their entire codebase!
If you ask yourself you can reuse library apps on multiple offerings? Yes, you can! What I would advice though is to make sure you always release all your offerings at the same time, so that there is no conflict in the version of any library apps, and you always have the right version of any of the apps available when necessary!
As such, that is quite easy to do with DevOps (don’t know with AL-Go for GitHub …): just make sure you create one release pipeline with all your corresponding offerings… .
Here is an example of our release pipeline of all our offerings. Left you see all apps, in the middle, you see all validations – and if they all work (every night, this is tested), you can release to AppSource (hopefully soon fully automated – waiting for Microsoft there ;-)).
It pays off to invest in DevOps knowledge .. just sayin’… 🤪
I truly realize that this “pattern” (if I can call it like that) is not for everyone. I know we have been quite “extreme” in code encapsulation, in modular design, in this kind of architecture .. taking “dependencies” to the extreme, may be. But to be honest – it has served us well.
We have had unplanned opportunities for parts of our product to be used at our sister-companies, and with immediate success. ONLY because we were able to send a subset of our apps, this was possible.
And again: it was completely unplanned. And that is key, isn’t it? How do we predict the unexpected?
All of this makes me think … we’re all very capable to come up with very good designs given the parameters, the requests, the scope that we have today.
But for damn sure it’s difficult to look into the future. We NEVER know if today’s scope, will be the scope of tomorrow. We might have a plan to sell only ABC. But tomorrow, somebody needs only B .. . Or we might have analyzed that being able to extend just 2 methods is enough … but tomorrow, we need to be able to extend many more.
We simply cannot look into the future.. but everybody expects us to be ready for it..
THAT’s why we need to try to be as flexible as at all possible.
THAT’s why we need to try to be as future-proof as at all possible.
THAT’s why we need to try to be as cloud ready as at all possible.
THAT’s why we need to try to be as extensible as at all possible.
THAT’s why we need to try to be as upgradeable as at all possible.
Or in other terms – THAT’s why we implement the Generic Method Pattern and other extension-patterns, implement the modular design as described above, try to be as flexible as at all possible with and on DevOps, .. and since recently … that’s why we will always upload “offering apps” on AppSource🤷♂️. Because, you know, may be tomorrow, we’d like to include yet another app in the offering, that wouldn’t have been possible otherwise.. ;-).
End of my still-in-progress story. Catch you in the next one!