Skip to content

Refactor App Architecture for Improved Separation of Concerns and Infrastructure Decoupling #8

@EchoEllet

Description

@EchoEllet

The goal of this refactoring is to:

  • Decoupling infrastructure code from the app-specific code, the app code should not be able to depend on dependencies like dbus, http, dart:io, or external services directly. Infrastructure code should not contain any app-specific code directly.
  • Clear responsibilities and strong separation of concerns.
  • Reusable and applicable to other apps. This will allow us to publish part of the Kraft Launcher code as separate packages in the future if we decide to.
  • Fewer regressions and bugs.
  • Less frequent changes to the app-specific needs and requirements. Better maintainability.

Here is how we should do it and how to do it:

Avoid mixing infrastructure code and data code in the data layer, and some data layer code is in the logic layer when it should be in the data layer

Right now, we're mixing infrastructure code and data code in the data layer, and some data layer code is in the logic layer when it should be in the data layer:

  1. Move repositories from the logic layer to the data layer.
    Repositories that are app-specific are in the logic layer, where they should be in the data layer, which indicates this is an infrastructure layer rather than a data layer, but classes like MinecraftAccountApi don't use raw data; they do some mapping for both success and failure responses. They use a sealed class for failures for type-safety, which is app-specific.
  2. Extract low-level reusable code from the data layer to external packages.
    Avoid any app-specific mapping or logic to decouple it from our app, also rename them to make sense for more generic use-cases, for example, omit the source suffix (e.g., Api, File), since in the context of the package, it's probably already clear and it should not be specific to our app. Classes like MinecraftAccountApi should be less generic and more specific to the source, since it communicates with api.minecraftservices.com; it should be called MinecraftServicesApiClient. It should still use Result pattern (not app-specific) but return raw data as is without any modifications, transformations, or mapping. Even for failures, return the response from the server without mapping to a sealed class that is type-safe.
  3. Mappers stay in parts of the data layer, so the data layer is there to fulfill the app's needs.
    Mappers that map the raw data to app-specific data should not be part of the infrastructure layer but rather the data layer.

Tip

The data layer is not limited to data persistence; it uses infrastructure code (from the new external extracted packages) to meet the app requirements.

This solution is powerful since we won't make low-level data sources with app-specific repositories in the data layer. It's also not very complex.

The logic layer should be renamed and modified:

  • It contains domain/business logic and application logic. We don't need to decouple the business logic from application logic. This is a Minecraft launcher, not a banking or social media app; we don't have many important business rules.
  • The UI layer contains ui logic, and the data layer contains data logic, which is why this name is not very clear.
  • I suggest the core or behavior layer, but I haven't decided yet. application is also good, but this layer also contains business logic, not just application logic.
  • Even though the infrastructure code will be decoupled as packages, so http, for example, is not a dependency of our app, the packages that depend on http will still be part of the app dependencies, so we should decouple the core layer from these packages that we develop, by following Dthe ependency Inversion principle, by definining an interface in the core layer on what it needs, the data layer is how it do it by using these reusable packages as part of the infrasturcture layer and adapt it to the app needs in the data layer (implementation of that interface). Infrastructure is technical and not specific to our app, so the code should be reusable for other apps as well.

Update our docs/ARCHITECTURE.md

Update it fully to avoid the confusing architecture and explain everything in here. For example, explain what infrastructure code could have, where it's usually found, or Data layer vs Infrastructure layer, keep our guide beginner-friendly with resources so everyone on the team can contribute and understand it quicker and easier.

Explain the following:

  • Are Flutter Blocs/Cubits part of the presentation or application layer?
  • Widgets should remain dumb.
  • Why not Domain layer or strict Clean architecture with use cases pattern?
  • There is a lot more, documented locally for now, since this project is in early stages and I'm the only dev for now.

Avoid having the features directly inside kraft_launcher/lib for low-coupling

Having all of the app code in one package inside lib (e.g., lib/account/data,ui,core) does not meet the project's needs and scalability.

Instead, we will have the following structure:

  • kraft_launcher: The Flutter app package that depends on all packages. Not allowed to depend directly on infrastructure packages that change over time (such as packages/minecraft/minecraft_services_client), instead it should depend on app/domain package (e.g., app_packages/account/minecraft_account_repository).
  • packages: Reusable and fully decoupled packages from our app code and project requirements. Should apply to any other app.
  • app_packages: App-specific packages, can depend on packages to meet app requirements without coupling the code to the infrastructure or external services. For example, app_packages/account/minecraft_account_repository could be the only place that can depend on packages/minecraft/minecraft_services_client.

See also: #8

Roadmap

  • Migrate away from throwing exceptions and use the Result pattern with failures instead.
  • Extract the ProjectInfoConstants into a separate package, independent of kraft_launcher.
  • Avoid throwing exceptions for expected failures and avoid verbose import prefix such as as microsoft_auth_api_exceptions. Prefer the Result pattern.
  • Cleanup the following dirty features:
    • account
      • Extract AccountRepository and its dependencies into separate packages. Consider a more suitable name and design, such as LauncherAccountRepository, which is more specific to a Minecraft launcher.
      • Extract Microsoft Auth code and device code flows into separate packages, consider refactoring the design if needed.
      • Redesign Account resolver, refresher, and account services (too general). Look at the TODOs for more info.
      • Fix the mess in the AccountCubit and MicrosoftAuthCubit. Look at the TODOs for more info.
    • settings TBD
    • launcher TBD

Metadata

Metadata

Assignees

Labels

architectureProject and code structure, including high-level design and organization.

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions