GWI favourites challenge#70
Conversation
…jection based on env
alexandros-georgantas
left a comment
There was a problem hiding this comment.
Hello @emanonk 👋
Thank you for taking the time implementing this assignment!
I have left some comments and I would like to hear your thoughts.
Thanks,
Alex
| Id string `json:"id"` | ||
| Name string `json:"name"` | ||
| Description string `json:"description"` | ||
| UserId string `json:"userId"` |
There was a problem hiding this comment.
Could you explain why both asset and favorite have user correlation? Is user id here needed?
There was a problem hiding this comment.
for the favorite I need both UserID and AssetID to link an asset with the user. For assets I treat them as a separate service. So the user creates an asset based on their input attributes. We still want to make sure that this asset belongs to one user. So, an asset that belongs to a user does not mean that it will be in their favorites. Also we might have assets that the onwer is one user and has shared permissions to other users to view.
|
|
||
| import "time" | ||
|
|
||
| type Asset struct { |
There was a problem hiding this comment.
Could you suggest a different type for the Asset entity?
There was a problem hiding this comment.
based on my comment about the GetAllAssets, I can change that to
type Asset struct {
ID string
Type AssetType
Name string
Description string
UserID string
CreatedAt time.Time
UpdatedAt time.Time
Insight *InsightData
Audience *AudienceData
Chart *ChartData
}
so something closer to the DTO but cleaner with the type to indicate what is not null.
| ) | ||
|
|
||
| // AssetRepository defines the minimal interface the application needs from a repository. | ||
| type AssetRepository interface { |
There was a problem hiding this comment.
what will happen in the case that someone wants all the available assets at once?
There was a problem hiding this comment.
For the exercise, I assumed each AssetType could come from a different source/service. To keep it simple, I modelled three separate types/maps. If we wanted “list all assets,” I'd have stored Asset and have 3 tables for each asset type, and then I would join them.
| return page, total, nil | ||
| } | ||
|
|
||
| func (r *FavouriteRepository) Save(_ context.Context, fav domain.FavouriteEntity) error { |
There was a problem hiding this comment.
Could you describe what will happen if at the same time a user marks an asset as favorite another user deletes that asset? (I've checked that you haven't provided this delete action in the assets repository but lets say that exists). Is the Save implementation protected from that behavior? If not could you suggest how this could be addressed?
There was a problem hiding this comment.
For this exercise there is not consistency between the favorites and assets store. Which it will lead to an orphan favorite. 1 option is to add a getAsset while saving the favorite, and a hook when deleting an asset to delete the favorite, but again not ideal. In the real world, if assets and favorites live in the same DB, then we can use a foreign key. and depending on the business rules, either to reject deletion or cascade the delete to favorites.
In case of separate storage and service, again base on the requirements, we can either use soft-deletes and then give time to user to recreate the asset and have the ownership on it. which is nice because things are not suddenly disappear from the users dashboard. or to emit an event from Asset and favorite service to subscribe to that event and clean up the favorite. That accepts eventual consistency but guarantees a valid state. Another option can be SAGA. but I think for this case eventual consistency is good.
|
|
||
| type Middleware func(http.Handler) http.Handler | ||
|
|
||
| func Chain(h http.Handler, m ...Middleware) http.Handler { |
There was a problem hiding this comment.
could you explain what is the purpose of this function?
There was a problem hiding this comment.
In this exercise, it is not useful as I have only one Middleware. If I wanted to add a logging middleware for example the reverse func helps to execute the middlewares in the same order that they are listed. So if the developer want to log->auth->handle then it writes
api.Use(LoggingMiddleware)
api.Use(skipAuthForPaths(jwtCfg, "/health")) which has a result logging(auth(handler))
| r.mu.RLock() | ||
| defer r.mu.RUnlock() | ||
| out := make([]domain.FavouriteEntity, 0) | ||
| for _, f := range r.data { |
There was a problem hiding this comment.
Assuming that the GET /v1/favourites API is heavily used by clients, would you still have r.data map using favourite ID as the map key? What are the reasons for choosing favourite ID as your map key?
There was a problem hiding this comment.
You are right, favourite ID as a key wont be efficient for this case. I initially used favorite ID as the key because it’s the natural identifier for update/delete operations with O(1). But given that requirement I could have used map[userId]map[favId]Favourite, as we alwyas lookup for specific user favorites. In real world this could be an secondary index, and in save we can have a unique constrain on user_id & asset_id.
| ) | ||
|
|
||
| type FavouriteRepository struct { | ||
| mu sync.RWMutex |
|
|
||
| type FavouriteRepository struct { | ||
| mu sync.RWMutex | ||
| data map[string]domain.FavouriteEntity |
There was a problem hiding this comment.
If you were to use persistent storage (e.g. a database), which technology would you pick and why?
There was a problem hiding this comment.
I think the assets and the favorites makes a lot of sense to be in the same service. I would prefer MySQL or Prostgres as favorite is a reference table to assets. (easy to Join tables). But Mongo can also work and I would considered it more if we had to store asset snapshots or if we wanted a more flexible asset schema. Another factor is always the team familarity, but thats another discussion.
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
| defer cancel() | ||
| if err := server.Shutdown(ctx); err != nil { |
There was a problem hiding this comment.
Great job with the graceful shutdown 💯
No description provided.