I want to stop using the usual force-app/main/default
single folder project structure in order to better organize my projects and decouple dependancies.
While researching about this practice, I found different examples:
- easy-spaces-lwc github repository, related stack exchange question suggesting it, and related blog post. Referenced as well in the official documentation.
- sfdx-falcon-template github repository, related stack exchange question suggesting it, and related blog post.
When going through the examples, I came up with the following questions:
-
What does the usual
force-app
folder aim to?- In the easy-spaces-lwc example, such folder is replaced by
es-base-code
,es-base-objects
,es-base-styles
andes-space-mgmt
. - In the sfdx-falcon-template example, it seems there is even one more level of organization with the
sfdx-source
folder, and inside of there,force-app
is replaced bymy_ns_prefix
,unpackaged
anduntracked
. Is this assumption correct?
- In the easy-spaces-lwc example, such folder is replaced by
-
What does the usual
main
folder aim to?-
In the easy-spaces-lwc example, within each respective top level folder, there is just the usual
main/default
folders. -
In the sfdx-falcon-template example, within each respective top level folder, there is not only the usual
main
folder, but also one folder for each feature. Where maybe one feature can correspond to, for example, one epic, since I understand it as a set of coupled funcionalities, which, if wanted, could usually be delivered as a different 2GP or unlocked package. On the other hand, with the organization structure from the easy-spaces-lwc example, this will not really work, since each top level folder (es-base-code
,es-base-objects
,es-base-styles
andes-space-mgmt
) does not correspond to a feature, but just organizes metadata types, mainly for deployment purposes. Is this assumption correct? -
If I assume my previous point about feature folders is correct, and could usually be delivered as 2GPs or unlocked packages, wouldn't I need to specify each feature folder at the same level of
force-app
ormy_ns_prefix
in order to be able to deliver them as such, as suggested in this blog post? -
Also, assuming we want to deliver them ad different 2GPs or unlocked packages, wouldn't it be better to have them in different repositories instead, as suggested in this stack exchange question? So far, I would work in a way where the whole project itself would be what I would have in one single repository.
-
-
Is the
default
project really needed?-
If so, does it need to be inside of a
main
folder? -
If not, how does sfdx understand where the default location for newly retrieved/pulled metadata should be? Since in the
sfdx-project.json
only the top level folder is sepecified as default. For example:force-app
,es-space-mgmt
,sfdx-source/my_ns_prefix
I ask this because in the sfdx-falcon-template example, within
my_ns_prefix
, feature folders directly contain the metadata folders suchs asclasses
,aura
,layouts
, and so on; but themain
folder, is structured inside with thedefault
folder, but also withdomain
,schema
,service
,test
andutility
, and each of them contain the metadata folders. So it seems that themain
folder has even one more level of organization.- Which is the exact purpose of each folders in this type of project structure?
-
Based on the information I have so far, I would like to came up with an approach similar to the one suggested in the sfdx-falcon-template example, but instead of having feature folders at the same level of my_ns_prefix/main
I would rather have them at the same level of my_ns_prefix
to have the possibility of delivering them as separated unlocked packages, being all the project in one single repository. Something like this (please note that I am intentionally removing main/default
from extra feature folders, since default
there will be only one, and instead of having different modules at main
level, there are at force-app
level):
sfdx-project-dir
└─ sfdx-source
└─ core-functionalities
└─ main
└─ default
└─ objects
└─ MyObject__c
├─ fields
└─ MyObject__c.object-meta.xml
└─ domain
└─ classes
└─ schema
└─ customMetadata
└─ customMetadataTypes
└─ customObjects
└─ customSettings
└─ platformEvents
└─ service
└─ classes
└─ utility
└─ classes
└─ extra-functionality-one
└─ domain
└─ classes
└─ schema
└─ customObjects
└─ service
└─ classes
└─ extra-functionality-two
└─ domain
└─ classes
└─ schema
└─ customObjects
└─ service
└─ classes
└─ unpackaged
└─ untracked
Would you say this is an scalable project structure? What changes would you suggest to it?
Although I understand that there is not only one single way to properly organize your project, I would like to know the exact meaning and purpose of each folder to take my decissions on how to name and organize them, and I would also expect to get the opinion of people with experience in this kind of modular develoment. I have found various reputable sources about this but It would be great if I can consolidate them here, being up to date.
Best Answer
Yes, these are two Salesforce-published examples, two different possible ways to organize metadata. Both of these, and other ways, are valid.
It's the default directory when you create a new project. It's just meant to serve as an example, though a lot of people use it, either out of laziness (it's already there), or under the impression it's somehow magical. Easy Spaces disproves it, but not everyone seeks out the documentation, and just start off with force:project:create, which creates these directories for you.
This is great for separate, potentially unrelated packages, especially if you plan on building dependencies.
Yes, there's no rule that all projects must start in the project root. This makes it clear what's not package directories, and what is. Here, they have managed package code, unpackaged code for Package Version validation, and everything else that's not destined for use in a Package.
Main is the default "feature" directory that everything gets dumped to when you force:source:retrieve or force:source:pull. If you don't have one, it will be created for you.
If you look at sfdx-project.json for Easy Spaces, you'll see that base-objects and base-styles stand alone, while base-code depends on objects, and mgmt depends on objects, base-code, and base-styles. These are packages with dependencies.
Note that ES shows off creating dependent packages, while Falcon serves as an example to ISVs that want to use Second Generation Managed Packages to build their AppExchange apps. Everything for one package needs to be in one directory, which is why it's organized by feature.
No. You can have your folders organized almost any way you want, as long as you don't violate the "magic directories" rule. For example, you can't have a directory called
classes
that contains non-Apex code (e.g.classes/objects/Account/fields/Industry.field-meta.xml
would be invalid, butclasses/utils/maths/MathUtil.cls
would be acceptable).The only other rule is you can't nest path folders. This can cause intended side effects. Just don't do it. Each package must be in its own, unambiguous space.
Splitting in separate repositories only really makes sense for completely different apps where there's no shared metadata. Once you want to start doing dependencies, it's far easier to have a central repository that stores the sfdx-project.json file; most IDEs and so on work better with one repo than many.
That said, smaller repos have better performance, less time spent pushing/pulling/merging. The downside is that you might need to synchronize files across several repos if you do this. There's also a third option, which I believe is a happy medium, and just use git submodules.
With submodules, you can have a main repository to work on, but each submodule will be an additional repository that can be updated independently. This cuts down on clone/pull/push times, as you can only pull in metadata chunks that you need for a project, but still have a way to keep your main version numbers and package IDs in a central location.
default
is where previously untracked components end up when you force:source:retrieve or force:source:pull, unless it already exists in a different feature folder. If you don't adefault
already, it will be created for you.Yes,
main
is the place wheredefault
lives. Again, if you don't have one, you'll get one, whether you like it or not. Metadata found in the package in another directory will be updated if it is found, but if not, it will end up inmain/default
.Yes, it does. This is how it should probably look after being massaged into submission. Again, sfdx doesn't know for sure, so, if the metadata doesn't exist, it'll create something like
main/default/classes
(e.g. for new Apex code). If you don't want this to happen, create the file locally, push, and then you can edit on the server.The purpose is organization. In the Falcon example, there is core metadata used throughout some/all of the features, and the features themselves contain metadata related to a specific feature. This can make it easier to navigate if some care is taken in how it is organized. The names are intentionally generic, because it's a sample, but you could have features like 'credit-check', 'delivery-system', 'customer-service-enhancements', etc. Or not. It's really up to you/your team to decide how it should look. At my job, we generally organize by core functionality, such as a section for utilities, one for each major function in the app (e.g. admin, trigger framework, etc).
As long as you remember the "Create Locally, Push, Edit Remotely" rule of thumb, you should mostly be okay. You'll end up spending some time having to move files around manually, but for most projects, it seems workable. Of course, you might discover you need to make some changes later (such is the way with doing new things), but I think you're on the right track.
Ultimately, I don't believe there is a One True Design that must be followed for success. If you can look at the directories and easily understand what their purpose is, it's probably an acceptable design. Just remember that you can't violate the "magic directories" rules, and you'll be fine.