New Side Project: Skedyol.com! January 4, 2025
I launched a new side project
I could tell you all about it, but that's already on the website here. This is my nerd site so get ready for some nerd stuff :)
After a few months of getting a lot of emails about my daughter's Altar Server schedule from admins and parents, I decided to build the site. Hopefully it's a lot easier.
Now onto the nerd stuff.
It has been a while since I worked on something using Google Cloud, so I was a bit rusty. My tools were out of date. This site, that you're reading this on, is built on Google Cloud. But it is targeting Go 1.14 whereas the new site is Go 1.23. So it's been a while.
It don't think there's anything magical about about the new site. Though I do like working in vanilla Go with handlers. The new ServeMux is nice! But a long time ago, I developed websites using Jakarta Struts. Oh man, just typing that out makes me feel old.
Struts were a bit above handlers in the abstraction layers. Go handlers are given just the request and the response objects. With Struts, you got those but you had some more information about the page and the session, basically a bit more strongly typed, hence my "layer above in abstraction" comment.
However, with the new ServeMux in Go, at least you know if a handler will only be used for GET or for POST. This site doesn't do any POST handling. But I have built sites in Go that don't import Gorilla Mux and have to check the method. But anyway, I split them out so that I don't have to check the method in skedyol.com.
One thing that I hadn't had any experience with but got a ton of experience with this site... well, there are a few of those. But one of them was emails! I've sent emails before with code. But this was having my own domain, setting up my DNS with email entries so that sendgrid can work, but also so that Google and other email providers don't immediately nuke it as spam. I even looked into setting up a BIMI record! If you don't have to Google that, then you know how legit I'm trying to make it. Otherwise, feel free to Google it :) In the past, you'd set up your own SMTP server and just send with that, and people would receive it! It was the wild west before spam. Now there are all of these authentication measures, and you'd be lucky to not end up in spam. But I disabled the tracking inside of sendgrid, so it should get through. But it's also a small community, and we can just tell people to check spam.
Another thing that I got a ton of experience with is Google Datastore, which is Google's nosql database. I used it on this site, but I'm not really doing anything with it. There's one object type on this site, a "Post", and it reads all of them and does filtering in code against the cache. When I create a new post, I write it to the database and refresh the cache. On Skedyol, I had to really figure it out. It works quite well, but I don't love how I did some things and will likely re-work some of it. There's a concept of a "Schedule" and a "Day", and to get a day, you need the Schedule and the day number. I think in the future I'd just use the "DayKey" and pass that where it is needed. There's also a Time concept and a similar situation. I'll look into making that better.
Basically, all of my forms that work against a time have a bunch of hidden inputs, for schedule, day, hour, minute. I'd like to just pass around the TimeKey, which is a UUID, and is used to make the key in Datastore. It's quite nice.
There's a concept of a Schedule, as mentioned above. This website has gone through at least two significant rewrites in its short life! The data access / data structure part of it. I originally had the schedule have an array of days, which would have an array of times, which would have an array of children assigned that time.
Then I thought about having multiple users updating this at the same time, having to update arrays inside arrays inside arrays... So I rewrote it to use separate "tables" for each type. Some parts of it would have been easier with a relational database, and SQL, which I'm extremely familiar with. But looking into it, the MySQL option within Google Cloud would incur a decent cost.
The document database method, which is datastore, requires some considerations for local development. I develop in VS Code, and test in Docker. In Docker, I construct two services, one with the backend and one with the front end. The front end has nginx, the backend just my Go program with any resources it needs, like text templates. Developing locally with a database that will end up functioning similarly to how Datastore works, there are a few options. One is to point directly to datastore for development. I tried that and it's a bit too slow for my liking. Another option would be to set up a Docker container with MongoDB or another document database. The other option is one I choose, which is to develop a JSON "database".
I used this technique with this website, and was like "oh it's so simple, let's do it that way!" But this site is just Posts, nothing else. I will probably look into another option if I were to do another website with this tech stack. I mean, it's the only tech stack that I use, so really I mean if I do another website. Probably will :)
But yeah writing a JSON data layer with the mild complexity that is skedyol.com added a bunch of time. When I was ready to write the data layer for Cloud Datastore, besides not wanting to update arrays within arrays etc, the JSON data layer had me locked in to a certain paradigm that I kind wanted to break free from, once I saw how easy it was to update an object within datastore.
How I write these data layers is basically I just have an interface{} in Go with the methods defined to read and write objects. Then I implement that interface for JSON, then later implement it for cloud datastore. And just switch between them with an environment variable. So when I was writing datastore data layer, and wanting to have it work a certain way, I had to go back and update the JSON datastore to work the same way, because I wasn't ever going to run this locally against my Cloud database :D Way too slow.
The benefit of doing something like MySQL is the only thing that changes between dev and prod (and any other environment) is a connection string. I might try that with the next site.
Another thing I rewrote when implementing the cloud datastore data layer, was I was basically doing business logic within the data layer. This became painfully obvious that it was wrong. And I know better! But when I had to basically reproduce the business logic for Cloud Datastore, line for line, but doing Cloud Datastore instead, I knew it was bad. So I rewrote it to have all of the handlers go through an application layer instead, and that worked against whatever data layer it was handed, and the data layers were rewritten to just do CRUD. It's been a while. This has been taught for decades for a reason.
Lastly, I was writing it with Bootstrap 5.3.3. It took me a lot of time to get into the flex thinking, and when I loaded the main bit of functionality on mobile for the first time, it resembled my nightmares. Bootstrap provides a lot of sample snippets for achieving certain affects on a website, and for that I was extremely grateful! I think the site looks pretty darn good!
In the past I've done SPA applications. I'd use Angular typically. I've grown out of all of that stuff. Give me vanilla javascript if at all, and save data by POSTing a form and redirecting. There are two fetch() calls that will do small updates, where it made sense to have something make a fetch(). I felt that for usability, predictability, performance, dependability, etc, for all of the good reasons, I chose to do it this way. With Go's html template and routes. Simple.
I do really like Go's http handler. I use C# and MVC a lot, and it really doesn't get you much more. Like, in C# you're able to write a method like this:
ActionResult GetEvent(string s, int y) {
// s and y are automatically populated from the request
}
and in Go you'll have to parse them from the request a tiny bit more manually
s := req.URL.Query().Get("s")
y := req.URL.Query().Get("y") // then parse int. this is fine.
I don't know. I like it. It doesn't try to treat me like a baby :)
Right now, the site is obviously up but the timing of it wasn't perfect for January, so in a week or two people will be signing up for it and using it. It'll be nice. I hope it's easy to use and people like it, and it's useful. That's all anyone can hope.
Some Git History and Stats
My first commit for this site was pretty bare. I had a test against sendgrid, and a basic index handler. It was on December 10th. My most recent commit happened today, January 3rd, at 5:23 PM, and I implemented a destination for login, like when you go directly to a page that requires authentication but aren't logged in, it'll send you to the login screen, but redirect you back to where you wanted to go originally. There are 96 total commits. There are 5,516 lines of Go across 32 files. Eh, that's a bit much but yeah some are pretty long files. There are 1,665 lines of HTML across 28 files. Not much you can do about that. I don't really care about lines, but it is a metric that shows how much code is in there, to an extent. I prefer short lines. I'm on a small laptop, 14" total. Yes, skedyol.com was developed on a tiny baby laptop :)
Happy coding!