Skip to content
Snippets Groups Projects

Create Cop to enforce the use of namespaced classes

Merged Fabio Pitino requested to merge enforce-namespaced-classes into master
3 unresolved threads

What does this MR do?

Related to #212156 (closed)

A healthy application is divided into macro and sub components that represent the domains at play. As GitLab code becomes broad with so many features and components it's hard to see what domains are at play.

We should expect any class to be defined inside a module/namespace that represents the domain where it operates.

The lack of namespaces today makes large parts of the codebase to have a flat structure:

  • class names from different domains may be confused because X may mean something in one domain and something else in another domain
  • it's hard to understand which group is responsible for the direction of the codebase (domain experts)
  • it's extremely hard to understand the interactions between components
  • etc.

This MR introduces a cop that flags class declarations that are not nested.

It's considered a first step towards defining better boundaries in the codebase.

This current implementation has a limitation. Compact class declarations like class MyDomain::MyClass are considered valid because we can't statically know if MyDomain is a class or a module. However if MyDomain is a class and it's indeed non nested, it will be flagged. Once it's fixed and all nested classes reorganized, the cop will be able to flag invalid declarations.

Screenshots (strongly suggested)

Does this MR meet the acceptance criteria?

Conformity

Availability and Testing

Security

If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:

  • Label as security and @ mention @gitlab-com/gl-security/appsec
  • The MR includes necessary changes to maintain consistency between UI, API, email, or other methods
  • Security reports checked/validated by a reviewer from the AppSec team

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
  • Fabio Pitino changed milestone to %13.8

    changed milestone to %13.8

    • Author Maintainer

      @grzesiek could you have a look?

    • Author Maintainer
    • Author Maintainer

      @gitlab-org/maintainers/rails-backend With this MR we are looking into enforcing the use of namespaced classes from now on. The .rubocop_manual_todo.yml contains the list of non-namesapced classes that we could look into migrating step-by-step into a namespace that represents the product domain.

      Feedback is very welcomed! :pray:

    • @fabiopitino thanks for doing this! I initially had a concern about things that do in fact cut across components, but most of the (non-model, non-controller) items that do so are already namespaced under Gitlab.

    • Author Maintainer

      @smcgivern I think the Gitlab:: namespace contains a lot of offenses to this rule that are masked by the Gitlab:: generic namespace. Ideally in future iterations we should look into excluding Gitlab:: namespace from the rule and see if further namespacing is present:

      • good: Gitlab::Ci::Config, Gitlab::Auth::AuthFinder, Gitlab::Database::BulkUpdate
      • bad: a lot of files directly under lib/gitlab/ and ee/lib/gitlab/ (not feeling like picking bad examples)

      In theory bounded contexts under app/ should have counterparts in lib/: E.g. Ci:: (in app) -> Gitlab::Ci:: (in lib), with exceptions to lib/ having more infrastructure/utility code. A good example is having all database tools under Gitlab::Database.

      Maybe we could have Gitlab::Loggers::, Gitlab::GitAccess::, Gitlab::Imports::, Gitlab::Projects::. The problem is that Gitlab:: contains both domain code as well as infrastructure code. These 2 layers should live in different directories.

      One goal we could aim to is to eventually move to a different directory structure like:

      domains/
        ci/
          models/
          controllers/
          finders/
          lib/
        merge_requests/
        issues/
        projects/
      gitlab/
        auth/
        database/
        elasticsearch/

      Where each domain has its own lib folder. This means that classes like Gitlab::Ci::Config will become simply Ci::Config as it's part of Ci:: domain. Then Gitlab:: namespace will represent the infrastructure code.

    • Yes, I think that's a good idea. And I think you're right - Gitlab:: code essentially is at the top-level namespace, for certain things at least :thumbsup:

    • @fabiopitino @smcgivern I agree! We should create an issue about this. How difficult would it be modify this cop to detect offenses in lib/? Do we need to have a separate issue? This might be a reasonable next step.

    • Author Maintainer

      @grzesiek It depends whether we want to eventually move domain related logic into domains/ (long term strategy) and leave infrastructure/shared-kernel code into Gitlab:: in lib/. In general I think it's good practice to have another nesting aside from the top-level Gitlab:: because it helps identifying the infrastructure domain.

      We could try to update the current cop in a different MR and see what other offenses come up. The current logic is masking them for now.

    • @grzesiek @fabiopitino I created #299181 (closed) to discuss making this check also apply to items directly under Gitlab::.

    • @fabiopitino I share your desired folder structure goal. A while ago I've made this proposal: #24725

    • Please register or sign in to reply
  • assigned to @grzesiek

  • 1 Warning
    :warning: This merge request is quite big (more than 1381 lines changed), please consider splitting it into multiple merge requests.

    Reviewer roulette

    Changes that require review have been detected! A merge request is normally reviewed by both a reviewer and a maintainer in its primary category (e.g. frontend or backend), and by a maintainer in all other categories.

    To spread load more evenly across eligible reviewers, Danger has picked a candidate for each review slot, based on their timezone. Feel free to override these selections if you think someone else would be better-suited or use the GitLab Review Workload Dashboard to find other available reviewers.

    To read more on how to use the reviewer roulette, please take a look at the Engineering workflow and code review guidelines. Please consider assigning a reviewer or maintainer who is a domain expert in the area of the merge request.

    Once you've decided who will review this merge request, mention them as you normally would! Danger does not automatically notify them for you.

    Category Reviewer Maintainer
    backend Serena Fang (@serenafang) (UTC-6, 6 hours behind @fabiopitino) Dmitriy 'DZ' Zaporozhets (@dzaporozhets) (UTC+2, 2 hours ahead of @fabiopitino)

    If needed, you can retry the danger-review job that generated this comment.

    Generated by :no_entry_sign: Danger

    Edited by 🤖 GitLab Bot 🤖
  • Fabio Pitino
  • removed 🍉 label

  • Grzegorz Bizon
  • @fabiopitino amazing work! Thank your for this merge request! :purple_heart: :100:

    I left a few questions.

  • I really like this as well!

  • Fabio Pitino added 1 commit

    added 1 commit

    • 397a6c82 - Apply 1 suggestion(s) to 1 file(s)

    Compare with previous version

  • Fabio Pitino added 834 commits

    added 834 commits

    Compare with previous version

  • Peter Leitzen requested review from @splattael

    requested review from @splattael

  • Peter Leitzen
  • Fabio Pitino added 1 commit

    added 1 commit

    • 4b2947f9 - Create Cop to enforce use of namespaced classes

    Compare with previous version

  • Peter Leitzen approved this merge request

    approved this merge request

  • Fabio Pitino added 1 commit

    added 1 commit

    • 028050b1 - Create Cop to enforce use of namespaced classes

    Compare with previous version

  • assigned to @rymai

  • Setting label(s) devopsverify sectionops based on ~"group::continuous integration".

  • Peter Leitzen mentioned in merge request !51572 (merged)

    mentioned in merge request !51572 (merged)

  • mentioned in issue #299181 (closed)

  • Fabio Pitino added 1 commit

    added 1 commit

    • fa486439 - Create Cop to enforce use of namespaced classes

    Compare with previous version

  • Fabio Pitino added 755 commits

    added 755 commits

    Compare with previous version

  • Rémy Coutable changed milestone to %13.9

    changed milestone to %13.9

  • Rémy Coutable resolved all threads

    resolved all threads

  • Rémy Coutable approved this merge request

    approved this merge request

  • Rémy Coutable enabled an automatic merge when the pipeline for 842ca2ea succeeds

    enabled an automatic merge when the pipeline for 842ca2ea succeeds

  • Rémy Coutable mentioned in commit 24c7171d

    mentioned in commit 24c7171d

  • Thanks @fabiopitino all everyone involved! :purple_heart: :muscle:

  • mentioned in issue #212156 (closed)

  • Fabio Pitino mentioned in merge request !51999 (merged)

    mentioned in merge request !51999 (merged)

  • Fabio Pitino resolved all threads

    resolved all threads

  • Author Maintainer

    Documentation MR: !51999 (merged)

  • mentioned in issue #299273 (closed)

  • Sean McGivern mentioned in merge request !49243 (merged)

    mentioned in merge request !49243 (merged)

  • added workflowcanary label and removed workflowstaging label

  • mentioned in issue #201855 (closed)

  • added workflowproduction label and removed workflowcanary label

  • Peter Hegman mentioned in commit 70e92714

    mentioned in commit 70e92714

  • Peter Hegman mentioned in merge request !51766 (merged)

    mentioned in merge request !51766 (merged)

  • Peter Hegman mentioned in commit f3f705f5

    mentioned in commit f3f705f5

  • mentioned in issue #299398 (closed)

  • Philip Cunningham mentioned in merge request !51296 (merged)

    mentioned in merge request !51296 (merged)

  • Peter Hegman mentioned in commit be4341e3

    mentioned in commit be4341e3

  • Philip Cunningham mentioned in merge request !51782 (closed)

    mentioned in merge request !51782 (closed)

  • Philip Cunningham mentioned in merge request !52291 (merged)

    mentioned in merge request !52291 (merged)

  • Fabio Pitino mentioned in merge request !52359 (merged)

    mentioned in merge request !52359 (merged)

  • Peter Leitzen mentioned in issue #299882

    mentioned in issue #299882

  • Fabio Pitino mentioned in merge request !52807 (merged)

    mentioned in merge request !52807 (merged)

    • @fabiopitino @splattael @ayufan @grzesiek I agree with the intent here, but I think this MR is a little too tight. There are valid cases where we want to embed a class inside another class, not just a module.

      In my case I'm extracting and replacing the traversal capabilities of the Namespace model. Namespace is a class, and it contains a collection of recursive query methods. These methods are specific to the Namespace model.

      class Namespace < ApplicationRecord
        def ancestors
        end
      end

      I want to do something like this...

      class Namespace < ApplicationRecord
        include Namespace::RecursiveTraversal
      end
      
      # app/models/namespace/recursive_traversal.rb
      class Namespace
        module RecursiveTraversal
          def ancestors
          end
        end
      end

      Which is invalid according to this cop. I think not being able to embed within a top level class will be a significant hinderance going forward. Do you agree?

    • @alexpooley Intersting example, thank you :bow:

      Personally, I am hesitant to use class also as a namespace and would prefer module instead. Should we create module Namespaces or similar? :shrug:

      Do you mind creating an issue to continue the discussion? :pray:

    • Thanks @splattael, I'm going to roll with Namespaces for now :bow:

    • Author Maintainer

      @alexpooley Thanks for this example! I agree with @splattael. Even Namespace class should probably go under a Namespaces:: context.

    • Please register or sign in to reply
  • Alex Pooley mentioned in merge request !52999 (merged)

    mentioned in merge request !52999 (merged)

  • Alex Pooley mentioned in merge request !52854 (merged)

    mentioned in merge request !52854 (merged)

  • mentioned in issue #300039 (closed)

  • Contributor

    From the frontend how should we best mirror the backend namespaces. In particular defining constants in frontend code.

    We also try to name vue apps and vue components/folders to the feature we are working on. But the strings are a bit fuzzy, not always mapped to any one namespace.

  • Fabio Pitino mentioned in issue #321982

    mentioned in issue #321982

  • Author Maintainer

    I've created a follow-up issue with next steps for fixing the existing cop offenses and foster discussion on more difficult offenses: #321982

  • mentioned in merge request !54793 (merged)

  • Vijay Hawoldar mentioned in merge request !55991 (merged)

    mentioned in merge request !55991 (merged)

  • Fabio Pitino mentioned in merge request !34042 (merged)

    mentioned in merge request !34042 (merged)

  • Philip Cunningham mentioned in merge request !61326 (closed)

    mentioned in merge request !61326 (closed)

  • Vijay Hawoldar mentioned in merge request !79657 (merged)

    mentioned in merge request !79657 (merged)

  • added typemaintenance label and removed tooling (archive) label

  • Peter Leitzen mentioned in issue #393158

    mentioned in issue #393158

  • Please register or sign in to reply
    Loading