12

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:

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 and es-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 by my_ns_prefix, unpackaged and untracked. Is this assumption correct?
  • 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 and es-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 or my_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.jsononly 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 as classes, aura, layouts, and so on; but the main folder, is structured inside with the default folder, but also with domain, schema, service, test and utility, and each of them contain the metadata folders. So it seems that the main 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.

2
  • 1
    I suggest to also have a look at docs.dxatscale.io and the linked video "Adopting Package Based Development at Scale" - the whole site and all resources there explain different aspects, paradigms, technical solutions and tools how a complex project can be driven with Salesforce DX Oct 19 at 13:27
  • Great site!! Thank you for sharing it Oct 19 at 23:30
5
+50

While researching about this practice, I found different examples:

Yes, these are two Salesforce-published examples, two different possible ways to organize metadata. Both of these, and other ways, are valid.

What does the usual force-app folder aim to?

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.

In the easy-spaces-lwc example, such folder is replaced by es-base-code, es-base-objects, es-base-styles and es-space-mgmt.

This is great for separate, potentially unrelated packages, especially if you plan on building dependencies.

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 by my_ns_prefix, unpackaged and untracked. Is this assumption correct?

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.

What does the usual main folder aim to?

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.

... this will not really work, since each top level folder (es-base-code, es-base-objects, es-base-styles and es-space-mgmt) does not correspond to a feature, but just organizes metadata types, mainly for deployment purposes. Is this assumption correct?

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.

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 or my_ns_prefix in order to be able to deliver them as such, as suggested in this blog post?

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, but classes/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.

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.

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.

Is the default project really needed?

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 a default already, it will be created for you.

If so, does it need to be inside of a main folder?

Yes, main is the place where default 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 in main/default.

So it seems that the main folder has even one more level of organization.

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.

Which is the exact purpose of each folders in this type of project structure?

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).

Would you say this is an scalable project structure? What changes would you suggest to it?

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.

1
  • Thank you for you detailed answer. There is one thing I would like to clarify though. About: "No. You can have your folders organized almost any way you want, as long as you don't violate the 'magic directories' rule." If I want to deliver each feature as a different unlocked package and these folders can be siblings of main instead of force-app, how will the sfdx-project.json will end up with the dependancies? I thought in the packageDirectories we could only refer paths of folders in the same level as force-app, but based on your answer I understand we can also have folders at the main level Oct 16 at 9:12
2

I've created a project with a non-default folder structure and also converted a different project back from such a structure as it had turned into quite a mess. And worked on many SFDX projects overall.

A key point is that the folder structure is purely how you arrange the components before they are deployed/pushed; once deployed/pushed the components are back into their default metadata types. There is no decoupling of dependences, rather you are categorising components by placing them in folders. Unwanted dependencies can still sneak in, and certainly over time cases will come up where something doesn't fit with the folder structure and also where people put things in the wrong place.

Individual file naming patterns can achieve categorization: domain term first, category second, and "Test" last. It's not hard to scroll up and down a long list of classes. To really decouple, separate project trees in separate Git repositories make more sense. Ideas like "this should really be a service" are best handled in design discussions and code reviews IMHO. I've seen pedantic layers built that smoke up the works more than they bring clarity.

The bi-directional push/pull of SFDX to/from an org using the standard folder structure is simple and elegant and helpful. But the pull is klunky to use with a non-default folder structure if you first create the component in the org because the component will be pulled to the default location not where you want it. You'll mostly be doing pushes but occasionally a pull saves a lot of time.

If your code base is large or you expect it to grow large then multiple packages are needed. But if you only want to sell variations in features, the Feature Management App (FMA) provides that capability for a single package. And with only one security review needed and only one package to install/upgrade.

If you re-read your question you will see that you have had to review and consider many options and there isn't an obviously best approach. Except IMHO the default SFDX tree. YAGNI might apply to any other approach but that is your call for your specific situation.

2
  • 2
    IIRC once a metadata item is pushed from path X it will always be pulled to path X, even when X is not the default path. A new metadata item is always initially pulled to the default path, but can then be moved to a different path before adding to VCS.
    – Phil W
    Oct 16 at 15:58
  • 1
    Hi @PhilW, Cool I didn't know that. Certainly wasn't the case a few years ago. Or perhaps I was creating components in the org and then pulling. I'll update the answer with the caveat.
    – Keith C
    Oct 16 at 16:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.