Skip to content

BCDA-9314: refactor API db connection globals#1171

Merged
michaeljvaldes merged 30 commits intomainfrom
mvaldes/BCDA-9287-Connection
Aug 7, 2025
Merged

BCDA-9314: refactor API db connection globals#1171
michaeljvaldes merged 30 commits intomainfrom
mvaldes/BCDA-9287-Connection

Conversation

@michaeljvaldes
Copy link
Contributor

@michaeljvaldes michaeljvaldes commented Jul 23, 2025

🎫 Ticket

https://jira.cms.gov/browse/BCDA-9314
https://jira.cms.gov/browse/BCDA-9287

🛠 Changes

  • moved database connection init logic to connect functions, which establish new connections to the database and replace init and global connection variables
  • split db connection and pool into separate connect functions with separate health checks
  • refactored files that relied on global vars (e.g. routers, middleware, apis) to structs with explicitly defined dependencies on database connections in apis, routers, and services
  • configured database connections at code entrypoints (e.g. cli setup) and passed connections down to services
  • refactored auth/Provider.go to not be a global, either, and instead be configured at setup and passed down like the database connections

To-Do:

  • refactor references to global connection in lambdas
  • refactor references to global connection in various other places (middleware, health check, etc.)
  • remove global connection var
  • add a unit test for pool connection check

ℹ️ Context

This PR demonstrates a strategy to refactor the database Connection -- specifically, to remove the global Connection variables defined in /database/connection.go which are created in an init() function, and instead do the following:

  1. Create the database connection(s) once at each code entrypoint (e.g. cli.go, each lambda main.go, etc.)
  2. Pass each connection as a dependency to the routers and services that need it.
  3. For each router/service that needs a connection, create a type with state that stores the connection, as well as a constructor that accepts its required dependencies, if necessary.

As a result, many other files needed to be changed to store the db connection within its state, including the API files and middleware. Notably, the Provider, which also depends on the db connection, was also being used as a global across many files, so it too had to be updated to a stateful type which is constructed at entrypoints.
Finally, many tests were updated to account for these changes. The majority of the test updates were completely innocuous -- replacing references to a global connection with references to a constructed connection -- but others involved untangling hidden dependencies on the database, provider, etc. that became very obvious after they were refactored.

🧪 Validation

app.Before = func(c *cli.Context) error {
db = database.Connection
r = postgres.NewRepository(db)
connection = database.GetConnection()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is an entrypoint at which we can create the database connection and pass it as a dependency to the routers and services that need it.

}

func init() {
func NewApiV1(connection *sql.DB, pool *pgxv5Pool.Pool) *ApiV1 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the example of the tech spec, we create a new struct for the API that clearly defines its dependencies (including the database connections!) with a constructor.

app.Version = constants.Version
app.Before = func(c *cli.Context) error {
db = database.Connection
db = database.Connect()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Construct connections and provider at code entrypoint to be passed down to APIs, routers, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we are no longer using the *cli.App for command line usage, it would be nice to eventually move away from this since this is where top level DI would occur. just a thought - way too big of a lift for this PR.

oversimplification, but something like:

type App {
    api      http.server
    auth    http.server
    data    http.server
    pool    sql.db/pgx
    c         config
    s         service 
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great idea, and I would vote to do this in a separate tech debt ticket that would remove the cli, if that works for you.

river.WorkerDefaults[worker_types.CleanupJobArgs]
cleanupJob func(time.Time, models.JobStatus, models.JobStatus, ...string) error
archiveExpiring func(time.Time) error
cleanupJob func(*sql.DB, time.Time, models.JobStatus, models.JobStatus, ...string) error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not suited for changes in this PR, but if we are updating the DB depenedencies, would cleanupJob and archiveExpiring be better suited as methods with CleanupJobWorker being the receiver? they would have access to db via CleanupJobWorker.db instead of having to pass it in. Thoughts on maybe leaving a comment on here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely, I actually thought the same thing and wondered why they were in separate files.
I left a comment for now but I'm open to changing it if folks feel strongly about it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can leave a TODO + a ticket for this one for future work to minimize risk?


func NewAuthRouter(middlewares ...func(http.Handler) http.Handler) http.Handler {
func NewAuthRouter(provider Provider, middlewares ...func(http.Handler) http.Handler) http.Handler {
baseApi := NewBaseApi(provider)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am certain this is user error, but I don't see the definition for this anywhere with cmd+f - does this func definition exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, which function are we talking about? If it's NewBaseApi, I created that in bcda/auth/api.go

carlpartridge
carlpartridge previously approved these changes Aug 6, 2025
@carlpartridge
Copy link
Collaborator

The two failing CI checks likely just need to update to latest changes on main.

@laurenkrugen-navapbc
Copy link
Contributor

  • cclf-import test integration / verify (pull_request)

these failing on the aurora name?

Copy link
Contributor

@laurenkrugen-navapbc laurenkrugen-navapbc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super happy to remove these inits from the codebase - thanks so much for knocking this out! 🎉

@michaeljvaldes michaeljvaldes merged commit 54152cb into main Aug 7, 2025
20 of 23 checks passed
@michaeljvaldes michaeljvaldes deleted the mvaldes/BCDA-9287-Connection branch August 7, 2025 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants