TCA Ideal file structure for production-level apps?

tldr: For big apps, would putting all Core files in one folder work better than separating Core files with respective view files based on their functions?

Our team is developing a huge production-level app with MVVM architecture. As we are refactoring our app to adopt TCA architecture, we have some concerns about how to reorganize our file structure.

This is our current structure:

App
├── Model
│ ├── User.swift
│ └── ...
├── Main
│ ├── MainView.swift
│ └── MainViewModel.swift
├── SignIn
│ ├── SignInView.swift
│ └── SignInViewModel.swift
├── ...
│ └── ...
.
.
.

Rather than putting viewModels in one file and putting views in the other, we have created folders based on views and put viewModels in related view folder. This structure is intuitive as files are separated based on their functions.

However, we noticed all sample apps with TCA provided by Point-free (like Tic-Tac-Toe example but much simpler than our app) have file structures that put core files and client files all together in one folder and view files in the other. If we use this structure, our structure will look like this:

App
├── Model
│ ├── User.swift
│ └── ...
├── Views
│ ├── MainView.swift
│ └── SignInView.swift
│ └──...(other views)
├── Core
│ ├── MainViewModel.swift
│ └── SignInViewModel.swift
│ └──...(other viewModels)
├── ...
│ └── ...

This structure works fine with the scale of Tic-Tac-Toe, but our app has dozens of views and viewModels. For instance, if we want to edit SignIn, we have to open up both the Core file and the view file with TCA structure, but we only need to open SignIn file with our previous structure.

Do you think enforcing TCA file organization system (putting Cores all together with views) works fine with big production level apps? ViewModels are basically Cores so we are curious which file structure would work with our app.

Just wanted to leave my five cents here.

We're also building an application with TCA and did consider how to organize our folder/file structure. In the end, we decided to go down a similar path as the one you're proposing. However, we split up the "ViewModel" file into a ViewState, ViewAction, ViewEnvironment and Reducer based on the Modularity section on PointFree.

App
⎣__Module
   ⎣__Main
   .  ⎣__MainView
   .  ⎣__MainViewState
   .  ⎣__MainViewAction
   .  ⎣__MainViewEnvironment
   .  ⎣__MainReducer
   ⎣__SignIn
   .  ⎣__SignInView
   .  ⎣__SignInViewState
   .  ⎣__SignInViewAction
   .  ⎣__SignInViewEnvironment
   .  ⎣__SignInReducer

I think keeping these components together in one folder makes it easier to understand which parts are pieced together and would recommend such a folder structure over the one chosen for the small case studies. Such a folder structure is also common practice for UIKit application and has proven to scale for bigger and even modularized applications, so I see no clear reason why not to adopt it. :slightly_smiling_face:

1 Like

Thanks for your response! Your architecture totally makes sense. One question here, if you split up the viewModel into state, action, environment, and reducer, have you ever had problems of managing too many files? I think this is something that is based on personal/team preference, but having 4 files for 1 view (5 including the view itself) seems a lot considering there can be dozens of views in a big app.

As you mentioned, folder and file structure comes down to personal preference. As these files stay rather small, one could also split it into two files (View / ViewState), but I wouldn't be sure how to name it. ViewModel is rather fitting, as it basically consists of ViewState, ViewAction, ViewEnvironment and Reducer.

1 Like

This is the structure I'm using in the app I'm currently working on :

  • Main
    • App (This is the main target that links the modules below)
      • Sources
        • SceneDelegate.swift
        • Core
          • AppAction.swift
          • AppEnvironment.swift
          • AppReducer.swift
          • AppState .swift
      • Resources (Assets catalog, Info.plist etc.)
    • Modules (Each feature lives in its own framework and has the same structure)
      • SomeFeature (Repeat Sources/Resources/Tests for each framework even for the supporting frameworks below)
        • Sources
          • Core
            • SomeFeatureAction.swift
            • SomeFeatureEnvironment.swift
            • SomeFeatureReducer.swift
            • SomeFeatureState.swift
          • Views
        • Resources
        • Tests
  • Support
    • Common (Shared code between main modules)
    • Design (Reusable UI Components)
    • Localization (Translations)

The key is to be consistent with each framework so it's easy to navigate them since you can end up with lots of them.

3 Likes

I’ve always organised my code according to what it is rather than how it’s used but that’s starting to seem rather unintuitive to me.

I now think that feature-related code should live together and library code that might be shared across features should live separate to that (potentially in a separate target or package).

I would also separate out common view components, including any design system code you might have, any common model code (generally value types) and utilities although I try and keep these to a minimum and ideally close to the code that uses it.

Feature-related code in a TCA app would be any views, controllers if not using SwiftUI. feature-specific value types, any view-specific state, actions and a reducer. Depending on the size of the feature, I find a single Feature/Core.swift containing the state, actions and reducer works well but you may want to separate these out further.

Because TCA allows you to break up features in such a logical way it makes sense to organise the code in the same way. It’s a starting point for potentially breaking features out into separate targets or making the features platform agnostic so they can be reused across different platform targets.

I also think this makes code easier to reckon with when you come back to it some time later. If you need to work on the “foo” feature you’ll know exactly where to find all the code specific to that feature.

2 Likes

You might like to watch The life of a file. It's about Elm and The Elm Architecture (TEA), not Swift, but TEA was a major inspiration for TCA.

Terms of Service

Privacy Policy

Cookie Policy