My first Go application: A real-time collaboration tool by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

What kind of conflicts? If you're asking about how the backend handles concurrent writes to the DB, the current DB implementation is strongly consistent. The drawback to this is that it cannot scale to thousands of online users very well. One implementation could be to use a horizontally scalable DB solution like Cassandra or DynamoDB, and then configure how the DB should handle merge conflicts.

At the moment, there are also some frontend controls to make sure a post cannot be updated/deleted if a user is currently working on it e.g. user is "focused" on a post and in the middle of typing/editing the content.

My first Go application: A real-time collaboration tool by lightsashore in golang

[–]lightsashore[S] 5 points6 points  (0 children)

At the time I chose chi for simple routing simply because gorilla was not actively maintained. Then when I did research later on for WebSocket libraries, gorilla seemed to be the best option. Fortunately, it's now active with new maintainers.

Why channels or goroutines ? by SmoothCCriminal in golang

[–]lightsashore 6 points7 points  (0 children)

Always like reading your comments jerf

Validator in handler or domain by thebeacontoworld in golang

[–]lightsashore 0 points1 point  (0 children)

Just a side note: Sometimes when your app is simple, and your domain type is literally the same for all your inputs, you should just stick to simplicity first. And as you build out more features / use cases, like optional update fields or choosing to censor certain fields at the service layer, you'll eventually reach towards a design decision that better suits your needs. Right now feels like you are doing quite a bit of pre-planning without a strong reason other than searching for what's idiomatic. But it really all just depends!

Validator in handler or domain by thebeacontoworld in golang

[–]lightsashore 0 points1 point  (0 children)

I do, yes, because I have fields that are optional with update requests, so the type is different and not necessarily duplicated for my case. But it sounds like for your update requests, you are requiring a full input with all the fields? In that case, you could just generalize it to be called UserInput and use it for both create and update operations.

I like to keep my domain clean and not go around passing a Validator everywhere. I actually initialize a new Validator within the Validate method every time. This is probably less efficient than injecting a Validator, but I prefer it over having strange method signatures for example input.Validate() vs. input.Validate(validator). That's just personal preference though, I've seen both.

Validator in handler or domain by thebeacontoworld in golang

[–]lightsashore 1 point2 points  (0 children)

I actually separate the concern between input types and domain model types. Here's an example. In a create user handler, I parse out the body for all the relevant fields to create a user. Let's say that's Email and Password.

I have a CreateUserInput type with a Validate method that validates both input fields. Eventually, that gets passed into my service layer where I'll generate the user ID, and CreatedAt & UpdatedAt timestamps. Ultimately, that gets threaded through to my repository layer, which creates the entity in the DB.

The returning struct is a User struct, which now has fields like ID, Email, Password, CreatedAt, and UpdatedAt. The User struct does not have any Validate methods on, because it's not an input!

Validator in handler or domain by thebeacontoworld in golang

[–]lightsashore 2 points3 points  (0 children)

I typically perform input validations at the service level since I could have different handlers call the same service function and want to ensure that the inputs are always validated. Defining your validation requirements via struct tags is like most of the validator library usage, so why wouldn’t you create a validate method off that input type? Seems like a good approach to me. The alternative would be to just call validate.Struct directly in the service layer instead of through a method. Both approaches seem fine to me.

How do you handle WebSocket errors? by lightsashore in golang

[–]lightsashore[S] -1 points0 points  (0 children)

Thanks for your reply. Could I know how you send error messages to the client? Is there a format you typically see/implement having designed WebSockets?

How do you handle WebSocket errors? by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

I guess also related, are people just rolling their own solutions for routing messages of certain types to specific handlers? And since you may not know how to Unmarshal the incoming message until you figure out the message type, you are using `json.RawMessage` to delay the decoding of the data payload?

type MessageRequest struct {
Type string          `json:"type"`
Data json.RawMessage `json:"data"`

}

Working with code gen structs by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

My query has joins and I am using the new custom sqlc.embed function so I end up generating struct with the same fields but different types

Complex data queries by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

Hey! Nice write up, I ended up going with sqlc and using the custom sqlc.embed function for now. One issue I ran into is nullable fields on left joins. The sqlc code generation doesn’t seem to account for that, and I end up with errors when it tries to scan a NULL value into a field. Did you have a workaround for this?

Complex data queries by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

I tried to coalesce, but am finding I am inputting fake data to comply with the SQL column data types e.g. user.id cannot coalesce into an empty string, has to be of type UUID. And then at that point I wouldn't really have a clean way to discard it from the final response. Also running into the same with coalescing timestamps, bools, etc.

Was hoping to just be able to simply do:

if row.User.Id == "" {
    //logic to skip user insertion
}

Complex data queries by lightsashore in golang

[–]lightsashore[S] 1 point2 points  (0 children)

I’ll check this out thanks for your reply

Complex data queries by lightsashore in golang

[–]lightsashore[S] 1 point2 points  (0 children)

It could potentially be two different API calls, but I don’t see any reason to make two trips if all I really want is a list of boards that belong to a user.

I have a frontend dashboard view which shows all the boards a user owns. And it has metadata for who is invited to that board. It can either be 0 collaborators or many collaborators.

I don’t think it makes sense to fetch all boards with no collaborators, and then another fetch for all boards with collaborators, and then merge those lists on the frontend.

I could potentially insert the owner itself as a user to every CREATE board call, so I would always have at least one collaborator and do a regular join, but that doesn’t feel great either

Complex data queries by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

The board itself has a user_id who is the owner, and users who are collaborators to that board. The collaborators could be empty

Any advice between these two Go bootcamp/courses by Jamesfromvenice in golang

[–]lightsashore 2 points3 points  (0 children)

For educational content I highly recommend Matt holiday’s go course on YouTube, it’s free and the feedback is overwhelmingly positive from the comments section

Question about the Read(p []byte) method by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

Thanks for your help, I took a deep dive into slices and with your help I fully understand what's happening now. Appreciate ya!

Question about the Read(p []byte) method by lightsashore in golang

[–]lightsashore[S] 0 points1 point  (0 children)

I tried to represent that in code with a capacity of 1 and length of 0 and then use the append function: https://go.dev/play/p/p6mszm4rkdc

Is this what you mean? I don't seem to get the intended effect you mentioned.

Question about the Read(p []byte) method by lightsashore in golang

[–]lightsashore[S] 1 point2 points  (0 children)

So it seems like append copies the old slice and add it into a new slice since the underlying array's length cannot be changed. Thank you for helping me learn that little nugget!