u/no_em_dash

I wrote an article about JavaScript iterators

I'm writing articles as a way to deepen my understanding of JS/TS and to help spread that knowledge. This one is all about iterators. I start with the snippet below and build up to custom iterators. Enjoy!

const arr = [1, 2, 3];

// `for...of` loops use iterators
for (const el of arr) {
  // do something
}

Learn more here.

reddit.com
u/no_em_dash — 4 days ago
▲ 5 r/golang

Most of my experience is with TypeScript but I want to use Go more often. I'm looking for guidance/opinions on web application design. I have read a dozen posts on that here but things still aren't really clicking.

Often when working on a web app I will start off with simple transaction scripts. But it doesn't take long before I'm introducing 3rd party services and background jobs into the mix.

A transactional outbox comes in handy in these cases but wiring it all together can be a chore. And as HTTP handlers grow testing becomes a pain.

So I was exploring some tools that can help simplify things while also setting me up for success later. I think `sqlc` and `river` are really great. I build a project to explore these tools and try out some hex arch patterns I've read about here.

I don't really like the way it turned out. So I'm trying to see if there is some middle ground. Here's what I'm thinking so far.

Imagine an up with some kind of user signup where we create a user in the DB and send a welcome email. I extract core logic and DB code from the handler like so:

type UserHandler struct {
  svc    *Service
  logger *slog.Logger
}

func NewUserHandler(logger *slog.Logger, svc *Service) *UserHandler {
  return &UserHandler{svc: svc, logger: logger}
}

func (h *UserHandler) HandleCreate(w http.ResponseWriter, r *http.Request) {
  // Parse JSON/form data to get values for an instance of `User`.

  id, err := h.svc.CreateUser(r.Context(), user)
  if err != nil {
    // Handle error.
  }

  // Send some response
}

The user service looks like this

type UserService struct {
  pool *pgxpool.Pool
  repo Repository
  jobs TXJobs
}

type Repository interface {
  Create(context.Context, User) (int64, error)
  WithTx(tx pgx.Tx) UserRepository
}

type TXJobs interface {
  InsertTx(ctx context.Context, tx pgx.Tx, args river.JobArgs, opts *river.InsertOpts) error
}

The idea Is that `Repository` and `TXJobs` starts to abstract away the details of `sqlc` and `river` but I'm still largely tied to Postgres. Still, the HTTP handler doesn't know about that.

The user service exposes this method which manages a transaction:

func (svc UserService) CreateUser(ctx context.Context, u User) (int64, error) {
  // Validate data here.

  var id int64
  var err error

  err = svc.atomic(ctx, func(tx pgx.Tx) error {
    id, err = svc.repo.WithTx(tx).Create(ctx, u)
    if err != nil { return err }

    err = svc.jobs.InsertTx(ctx, tx, WelcomeEmailArgs{}, nil)
    if err != nil { return err }

    return nil
  })

  return id, nil
}

func (svc UserService) atomic(ctx context.Context, fn func(tx pgx.Tx) error) error {
  tx, err := svc.pool.Begin(ctx)
  if err != nil { return err }
  defer tx.Rollback(ctx)

  err = fn(tx)
  if err != nil { return err }

  return tx.Commit(ctx)
}

From here I can evolve my user service independently of the HTTP handler. Testing with Postgres is pretty easy with `pgtestdb` so I'm not super worried about the coupling.

I would love to get some advice about this or the GitHub repo I posted above. Thanks!

reddit.com
u/no_em_dash — 24 days ago