Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/note feature transfer #17

Merged
merged 19 commits into from
May 2, 2019
Merged

Conversation

lkwdwrd
Copy link
Contributor

@lkwdwrd lkwdwrd commented Apr 22, 2019

Features and ideas created with the note service, transferred here.

  • ConnectionManager bootstrap and shutdown service connections gracefully in concurrently
  • DB Updates: Allow db object to work will with ConnectionManager
  • HTTP Helpers: Decode and encode JSON consistently
  • Logger: Configurable output logger, WIP
  • OS Env: Allow a default value version in addition to the MustGet version
  • Middleware: Consolidate functionality, add a TrimSlash middleware option.
  • Server: Remove server startup method -- too much abstraction
  • Test Helpers: Encode/decode helpers and a status response assertion with context (we should avoid assert functions in general, but this helper really made the note tests more readable).
  • Validation: Helpers for checking the encoding and formatting for E3DB style values

connectionmgr.go Outdated
// ConnectionManager manages initialization and shutdown of long lived connections.
// Each connection object must match the Initializer interface. Initialization happens
// in parallel. The waitgroup can be used to wait util all connections are initialized.
// Close kick off the proper shutdown of all managed connections.
Copy link
Contributor

Choose a reason for hiding this comment

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

s/kick/kicks

connectionmgr.go Outdated
@@ -0,0 +1,72 @@
package utils
Copy link
Contributor

Choose a reason for hiding this comment

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

Every consumer of utils doesn't need this file, it should be put into a separate package for this module. Suggest connection_manager.go

connectionmgr.go Outdated
}

// NewConnectionManager initializes a new ConnectionManager object that can be used
// to manage the life of long lived remote connections such as to a database.
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you look at/think about using context for this purpose? https://blog.golang.org/context

connectionmgr.go Outdated

// DoInit initializes an object matching the Initializer interface, setting its close
// operation to run when the Connection Manager's Close method is called.
func (cm *ConnectionManager) DoInit(initializer Initializer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is the major functionality of connection manager, I'd think about having the initializer be a field of connection manager and pass it in when calling New for connection manager.

database/db.go Outdated
@@ -59,16 +61,31 @@ func (d dbLogger) AfterQuery(q *pg.QueryEvent) {

// New returns a new DB object which wraps
// a connection to the database specified in config
func New(config DBConfig) DB {
func New(config DBConfig, logger *log.Logger) DB {
Copy link
Contributor

Choose a reason for hiding this comment

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

prefer passing logger in as a param of config versus changing the signature of this method

db := pg.Connect(&pg.Options{
Addr: config.Address,
User: config.User,
Database: config.Database,
Password: config.Password,
})
if os.Getenv("QUERY_DEBUG") == "ON" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Tears of joy to see this removed.

database/db.go Outdated
}
}

// Initialize starts up the database and returns the Close function used to gracefully shut it down.
Copy link
Contributor

Choose a reason for hiding this comment

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

this comment needs to be updated to be accurate, there is no database started, only an attempt to run migrations(which as a consumer of this code I would like to know is going to happen when I call this code), which I don't think should happen as it prevents someone from using this who wants to do something non standard/consider the database initialized in a different way.. Please keep in mind when adding code to this module that the purpose of utils is to provide flexible general purpose code, not become the abstract class that imposes behavior on consumers.

http.go Outdated
@@ -162,3 +162,25 @@ func HealthCheckHandler(serviceName string) http.Handler {
w.Write([]byte(fmt.Sprintf("%s service is up.\n", serviceName)))
})
}

// ReqToObject un-marshals a request object body JSON into an object
func ReqToObject(r *http.Request, obj interface{}) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Please follow the convention elsewhere in this code and most of go of spelling things out, I don't see any logic to why we would excludeuest but not 'ject' .

Copy link
Contributor

Choose a reason for hiding this comment

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

s/object/struct/

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest DeserializeJsonRequest as a more accurate, go'ish name.

http.go Outdated

// WriteObjectResponse marshals an object into the response body and sets
// JSON content type headers
func WriteObjectResponse(obj interface{}, w http.ResponseWriter) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest SerializeJsonResponse for this function name.

logger.go Outdated
@@ -72,3 +73,18 @@ func NewServiceLogger(serviceName string, level int) ServiceLogger {
}
return logger
}

// GetLogger initializes a logging object writing to the requested log file.
func GetLogger(logFile string, serviceName string) *log.Logger {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest the more go'ish name of "MakeLogger"

logger.go Outdated
@@ -72,3 +73,18 @@ func NewServiceLogger(serviceName string, level int) ServiceLogger {
}
return logger
}

// GetLogger initializes a logging object writing to the requested log file.
func GetLogger(logFile string, serviceName string) *log.Logger {
Copy link
Contributor

Choose a reason for hiding this comment

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

this should accept the interface it cares about (io.writer), not a magic string that is impartially checked for usefulness

validation.go Outdated
@@ -0,0 +1,43 @@
package utils
Copy link
Contributor

Choose a reason for hiding this comment

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

this should also be a separate subpackge

@lkwdwrd lkwdwrd requested review from efabens and galxy25 May 2, 2019 13:32
User string
Database string
Password string
Logger *log.Logger
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should this just be made a Service Logger at this point? Do we fully standardize on it?

Copy link
Contributor

Choose a reason for hiding this comment

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

This can be a service logger - its at the point now with the last updates Michael made to not have a logger per level and accept human readable log level that its ready for prime time/doesn't have any major usability issues I can see(take that with a grain of salt as I haven't used it in any of the code I've written)

Copy link
Contributor

Choose a reason for hiding this comment

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

It's my proudest go code. That aside, switching between the two is fairly painless and none of the consumer code for the logger will have to change.


// JSONLoggingMiddleware wraps an HTTP handler and logs
// the request and de-serialized JSON body.
func JSONLoggingMiddleware(logger *log.Logger) Middleware {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again -- wondering if fully embracing the service logger is better here?

@lkwdwrd
Copy link
Contributor Author

lkwdwrd commented May 2, 2019

@galxy25 @efabens - big updates based on reviews and conversations. Reset review status and now it could use another look.

The changes are a little heavy handed, but after talking with Levi, I decided to fully embrace some of the concepts.

The biggest potential breaking change is middleware. I moved the old middleware functions to live with the middleware type turned them into full fledged middleware functions rather than wrapping them.

Along with the change, I moved http utils into the server package. I actually wanted to name the package http, but since this would be used with the net/http package 99% of the time, I didn't think it was worth the naming conflict. server is the best I came up with for now, but happy if you have other thoughts. The utils are broken out into what feels like some logical files separations. I have no doubt this subpackage will grow as utils-go gets bigger. Maybe this should be the root utils package?

I refactored the service logger with some ideas based on conversations. I'm hoping the API is a little more flexible. It is now, essentially, a decorated log.Logger, so things like setting log output, prefix, etc. are supported. Along with Printf style Print and Println are style log level functions are available as well. @willmichael - thank you for you work on this! This is not necessarily a better way, just different, and I wanted to get this version up for discussion. Please comment on this take if you have the time!

Connection Manager is broken out to manage Initialization and Close separately and the API for adding managed connections changed to have better names. More (better?) doc strings.

The DB struct initialization was refactored based on the golang bufio.Scanner struct in terms of how to allow Initialization method customization. See buffio.Scanner.Split.

Naming updates, sub packages. Sorry for the mess!

cm.ManageClose(ic)
cm.ManageInitialization(ic)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This file looks like beautiful go code :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

wow, high praise! Thanks!

}

// DB wraps a client for a database.
type DB struct {
Client *pg.DB
Client *pg.DB
Logger *log.Logger
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be embedded into this struct and not a public field(I can't think of a use case other code would have for needing access to the database's logger), with the additional upside that methods on this struct can make logging statements via db.Println() versus db.Logger.Println
https://golang.org/doc/effective_go.html#embedding
https://travix.io/type-embedding-in-go-ba40dd4264df

Copy link
Contributor

Choose a reason for hiding this comment

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

Note though that a service logger satisfies the logger interface so can be passed as the logger value here, and that the underlying Postgres client only accepts a logger interface so won't be able to take advantage of any additional methods we have(though our database package can)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I know log.Logger is still a concrete type, not an interface :(

golang/go#13182

@@ -0,0 +1,118 @@
package connectionmanager
Copy link
Contributor

Choose a reason for hiding this comment

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

connectionmanager feels like an eyesore to me, suggest package connection if it doesn't conflict with anything else?

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've fought a lot with this name. Happy for something better!

Connection isn't quite accurate since this manages the initialization/shutdown of any number of items as long as they support the initializer/closer interfaces. Indeed, connection is probably even wrong, though it nods to the intent of this managing things like database/elastic search connections. It provides a method of bringing them up and shutting them down as a group, rather than individually. I don't have a good name, so I stuck with connection manager. The file used to be called connectionmgr, but Levi set me straight on short names.

Copy link
Contributor

Choose a reason for hiding this comment

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

connectiongroup? Naming is hard, I'll come back to it if I can think of something...

// Closers are queued up internally running only when the ConnectionManager's Close method
// is called. The ConnectionManager runs each Close method in a separate go routine and blocks
// blocks until all are complete.
type ConnectionManager struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe a more idiomatic naming scheme would be to remove Connection prefix from the names within this package to avoid the stutter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Manager feels too generic to me as a name - but again, I can't seem to name this functionality. Help!

Copy link
Contributor

Choose a reason for hiding this comment

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

Manager as a whole is generic but it's within the context of connectionmanager: the consumer of the package will be typing connectionmanager.ConnectionManager repeatedly. But it could be connectionmanager.Manager or even connectiongroup.Manager

if sl.logLevel >= levelMap[level] {
sl.Output(2, sl.prefixString(level)+fmt.Sprintln(v...))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that we have this I think its safe/time to delete https://github.com/tozny/utils-go/blob/master/logger.go#L10

Copy link
Contributor Author

Choose a reason for hiding this comment

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

logger.go is removed as part of this PR (it was moved here and refactored).

Copy link
Contributor

Choose a reason for hiding this comment

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

Also much better comments than me 👍

Close()
}

// InitializerCloser is the interface the both initializes and gracefully closes a
Copy link
Contributor

Choose a reason for hiding this comment

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

that both


// ManageConnection accepts any number of items matching the InitializerCloser
// interface and manages both an item's initialization and close.
func (cm *ConnectionManager) ManageConnection(initializerClosers ...InitializerCloser) {
Copy link
Contributor

Choose a reason for hiding this comment

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

ManageConnection seems to restart a connection, close -> initialize -> return. Would a more accurate name be Restart?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It doesn't...

Though the ordering is a bit confusing, this queues up the close method of the managed item first (which will not run until the connection manager itself has the Close method called. Once queued, it then adds the items initialization function, which immediately starts a go routine that initializes the item. The order ensure the close function is managed before initialization begins.

The fact that you are asking means this is just a bit too confusing. I'll add additional comments to clarify.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh I understand, maybe EnqueueConnection/EnqueueClosers/EnqueueInitializers more accurately describes what they're doing?

w.WriteHeader(http.StatusOK)
w.Write([]byte(fmt.Sprintf("%s service is up.\n", serviceName)))
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

If you want these methods to live here(which seems reasonable), I think we should delete the duplicate methods here https://github.com/tozny/utils-go/blob/master/http.go

final = handler(final)
// ExtractBearerToken attempts to extract an Oauth bearer token
// from the provided request, returning extracted token and error (if any)
func ExtractBearerToken(r *http.Request) (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.

Please cleanup the now deprecated version of this in https://github.com/tozny/utils-go/blob/master/http.go

// Middleware is a function that decorates an http.Handler
type Middleware func(handler http.Handler) http.Handler
var (
// SupportedAuthTypes is a whitelist of supported authentication types. Deafult: Bearer
Copy link
Contributor

Choose a reason for hiding this comment

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

please delete now deprecated values for the below from https://github.com/tozny/utils-go/blob/master/http.go

// JSONLoggingMiddleware converts the utils JsonLogger into Middleware
func JSONLoggingMiddleware(logger *log.Logger) Middleware {
const (
// ToznyClientIDHeader is the headers key containing a client ID
Copy link
Contributor

Choose a reason for hiding this comment

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

please delete now deprecated values for the below from https://github.com/tozny/utils-go/blob/master/http.go


// HandleError is a generic error handler for responding with the given status and error
// using the provided ResponseWriter.
func HandleError(w http.ResponseWriter, statusCode int, response interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

please delete now deprecated values for the below from https://github.com/tozny/utils-go/blob/master/http.go

Copy link
Contributor Author

Choose a reason for hiding this comment

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

http.go is removed as part of this pr and broken up / added to the server subpackage

func New(config DBConfig) DB {
db := pg.Connect(&pg.Options{
Addr: config.Address,
User: config.User,
Database: config.Database,
Password: config.Password,
})
if os.Getenv("QUERY_DEBUG") == "ON" {
db.AddQueryHook(dbLogger{logger: log.New(os.Stdout, fmt.Sprintf("%s: %s: ", config.Address, config.Database), log.LstdFlags)})
if config.EnableLogging {
Copy link
Contributor

@willmichael willmichael May 2, 2019

Choose a reason for hiding this comment

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

Trying to think about how the Service Logger can be integrated into this, so we don't have to specifically enable at the DB level... But everything I can think of is slightly convoluted. Unless we want to restrict the DB logging to a specific level like debug

@lkwdwrd lkwdwrd merged commit ff4e135 into master May 2, 2019
@lkwdwrd lkwdwrd deleted the feature/note-feature-transfer branch May 2, 2019 21:29
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.

4 participants