Geo: use Dependency Injection to abstract out database fields

While I was having a look at !18886 (closed), I was seeing an increased use of things like .public_send("#{type}s_pending_verification") and repository_state_table["#{repo_type}_retry_at"].

I'm not a huge fan of that, so I want to suggest dependency injection as an alternative.

Proposal

(don't pin me down on the naming, it's just a proposal)

We introduce something like RegistryAccessor and SyncableRepoAccessor. These classes are "protocols" (also sometimes called "interfaces") which all derived classes should respond to.

When for example a AnyRepositorySyncService wants to sync a repository (it can be project repo, wiki repo, design repo, or whatever), it will given upon constructions an instance of RegistryAccessor and SyncableRepoAccessor. The object stores these in instance variables, and uses these to access properties of the repo it wants to sync.

Example

Let me try to explain with a crude example.

module Geo
  class AnyRepositorySyncService
    def initialize(repo_accessor, registry_accessor)
      @repo_accessor = repo_accessor
      @registry_accessor = registry_accessor
    end

    def sync!
      # Do the magic
    rescue
      failed!
    end

    def failed!
      @registry_accessor.failed!
    end

    def last_sync
      @repo_accessor.last_sync
    end
  end

  class WikiRepositoryAccessor
    def initialize(project)
      @project = project
    end

    def last_sync
      @project.last_wiki_synced_at
    end
  end

  class ProjectRepositryAccessor
    def initialize(project)
      @project = project
    end

    def last_sync
      @project.last_repository_synced_at
    end
  end

  class WikiRegistryAccessor
    def initialize(registry)
      @registy = registry
    end

    def failed!
      @registry.wiki_sync_failed = true
    end
  end

  class ProjectRepositryAccessor
    def initialize(registry)
      @registy = registry
    end

    def failed!
      @registry.repository_sync_failed = true
    end
  end
end

The Geo::AnyRepositorySyncService is not capable of syncing any type of repo, as long as you provide it with 2 objects that know how to read/write things (on second though, they don't have to be 2 separate objects).

So to sync a wiki:

project = Project.first

wiki_repo_accessor = WikiRepositoryAccessor.new(project)
wiki_registry_accessor = WikiRegistryAccessor.new(project)
Geo::AnyRepositorySyncService(wiki_repo_accessor, wiki_registry_accessor)

Or to sync the project repo:

proj_repo_accessor = ProjectRepositoryAccessor.new(project)
proj_registry_accessor = ProjectRegistryAccessor.new(project)
Geo::AnyRepositorySyncService(proj_repo_accessor, proj_registry_accessor)

Only at initialization you need to specify the type, once things are instantiated, no more dynamic method or column names are needed. The objects know where to find things.

Assignee Loading
Time tracking Loading