Closed mattkuznicki-ll closed 1 year ago
LGTM!
Thanks @pid1 - let's please leave this open at least a day or two to give others a chance to review before merging.
While this is certainly early days, I'd like to see us start with a more intentional and structured approach. I've also got some questions around the general approach here.
- I'm curious about the choice here between
gin
,chi
, and the built-innet/http
. I've generally preferredchi
, but I'm pretty flexible on this. Mostly want to make sure we're picking one on purpose.- I usually keep
main.go
s incmd/[usecase]/
: I've often found reasons to want to compile and run the logic in different ways, and this allows for pretty easy extension- I like to keep files specific to their use, especially
main.go
. For an HTTP service, that usually means thatmain.go
only contains: call to config package, dependency initializer, router generation (w/ dependency injection), and server start (w/ error capture, and ideally SIGINT capture)- I'm not a fan of an
internal
package. I usually separate it out into specific domains. We can probably nest them insideinternal/
, but I don't like a general catch-all package.- I want to keep the data model / business logic fully separated from the HTTP layer. The http layer should import the data model layer.
I think the closest I've got to an example would be: https://gitlab.com/mk5r/rialto-exercises , but that's not fully complete on those points: it was a super quick-and-dirty coding challenge for a job app, so it's missing some things (such as the logger isn't being dependancy-injected at handler generation). It's probably also correct to move
person/
intointernal/
there.
Thanks for the feedback @mkantzer! To your points:
While this is certainly early days, I'd like to see us start with a more intentional and structured approach. I've also got some questions around the general approach here.
- I'm curious about the choice here between
gin
,chi
, and the built-innet/http
. I've generally preferredchi
, but I'm pretty flexible on this. Mostly want to make sure we're picking one on purpose.- I usually keep
main.go
s incmd/[usecase]/
: I've often found reasons to want to compile and run the logic in different ways, and this allows for pretty easy extension- I like to keep files specific to their use, especially
main.go
. For an HTTP service, that usually means thatmain.go
only contains: call to config package, dependency initializer, router generation (w/ dependency injection), and server start (w/ error capture, and ideally SIGINT capture)- I'm not a fan of an
internal
package. I usually separate it out into specific domains. We can probably nest them insideinternal/
, but I don't like a general catch-all package.- I want to keep the data model / business logic fully separated from the HTTP layer. The http layer should import the data model layer.
I think the closest I've got to an example would be: https://gitlab.com/mk5r/rialto-exercises , but that's not fully complete on those points: it was a super quick-and-dirty coding challenge for a job app, so it's missing some things (such as the logger isn't being dependancy-injected at handler generation). It's probably also correct to move
person/
intointernal/
there.
Re: gin vs. net/http vs. chi at this point, there's not too compelling a reason to stick purely with net/http nowadays (frankly, even if you want a lower level interface you should be using fasthttp
anyways) and there's more an ecosystem around gin now which edges it out IMO. Both are fine and there are, IMO, more important technical decisions than this for this to be a blocking issue. Keep in mind, communication between services will be GRPC, this is only for the main REST API.
I like to keep files specific to their use, especially
main.go
. For an HTTP service, that usually means thatmain.go
only contains: call to config package, dependency initializer, router generation (w/ dependency injection), and server start (w/ error capture, and ideally SIGINT capture)
It's no problem at all moving the code around as more functionality is added. I generally do agree with the sentiment here but it's not really a blocker given the small amount of code.
I usually keep
main.go
s incmd/[usecase]/
: I've often found reasons to want to compile and run the logic in different ways, and this allows for pretty easy extension
This is also a great way to end up with subtle bugs if your interfaces have slightly changed in your various main.go's. This is not a practice I would generally endorse vs. a single CLI that can be run in different modes (which is a more industry standard way of handling eg; various deployment modes using a single binary).
I'm not a fan of an
internal
package. I usually separate it out into specific domains. We can probably nest them insideinternal/
, but I don't like a general catch-all package.
I agree. Ultimately the arrangement depends on what exactly you want importable as a library elsewhere. My thought model here is we won't really be importing much from this repo directly since what talks to this will be frontends including a mobile app down the line. So let's just keep everything in one package, ditch the internal package
and we can nest if needed as this grows. Not a big deal b/c you won't break downstream imports.
@mattkuznicki-ll Not quite, although in review it's likely I'm applying things early here.
I usually end up keeping 2 separate struct defs for API'd objects:
LasaganaLoveUser
). This would usually not actually have any json
tags, because it's only interacted with by other app componentsOn of the functions of the handlers is to convert between the two.
The basic idea is to be able to change either implementation without needing to change the entire application, and to keep each fully testable in isolation.
Guiding principal: your API should not be directly tied to ex: a database schema. They're related, but should not be required to be the same.
@mkantzer totally agree we don't want a schema that's directly tied to the database, nor to end up exposing database structure to callers directly. The database access is intended to end up in a separate microservice used by the Bechamel API, and I believe you'll see more separation once that gets closer to an implementation. Including the ability to test components individually. For the near term, the primary objective is to get something we can flesh out an API with so those working on the frontend have something to work with & code to. So it may be a bit before the backend becomes closer to its componentized system that we intend it to become.
Seeing 3 approvals, am merging. We can refine in issues and later PRs.
@mattkuznicki-ll, outstanding API sketch and authentication implementation. well done & thank you!
dev
✎ unit tests: Discussion on implementation
✎ mock data: Separating mock/stub data
✎ password hashing: Future plan
✎ frameworks: Gin, Chi, net/http consideration
✎ file structure: Organizing files and main.go
✎ data models: Separation of data models from HTTP layer (i.e encapsulating database access into a separate microservice)
Simple implementations for login (JWT authentication token acquisition), creating new user profiles, and obtaining profile for the current authenticated user + for a profile by ID.
This is very much a starting point and there's a lot missing from here. In no specific order, known issues include:
The intent of this addition is to give a starting point for project participants to be able to start working on the API and to give a concrete forum for prototyping potential API additions.
This is the author's first Go project - pointers and tips on style, conventions, ways to improve code or anything else are welcomed.