Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

usually for stores like this I just do this:

    type Store interface {
        GetTask(*Task) error
    }
instead of having "GetTaskByID" and "GetTaskByTag" and whatnot. Then in the caller you just do this:

    task := store.Task{ID: 5}
    if err := db.GetTask(&task); err != nil {
        // wahtever
    }
^ that gets the task by ID

    task := store.Task{Tag: "foo"}
    if err := db.GetTask(&task); err != nil {
        // wahtever
    }
^ that gets the task by tag.


The downside to this approach is that you're unable to know what fields you can use in your query without reading the GetTask function, and changes to the GetTask function can silently break all callers, since it's now a runtime error.


tbh I've been using this method for years and the first one has never been an issue, because practically speaking you should only expect to give a single struct with a field or two filled in if that field or combination of fields is unique. You probably need that level of domain knowledge about what you're working on elsewhere anyway, so it has never been a problem.

I mean ... for the second problem that's broadly true of making any changes to your data access layer since by definition your Go compiler is not going to, for example, check the validity of a SQL query. So ... yes? but that's not unique to this approach, that's true generally.


This makes sense, did you implement this alongside grpc / protobuf?

I'm curios about the way you handled zero values, field masks could be a solution, but I think it would get bloaty.


    func (db *actualStoreImplementation) GetTask(t *Task) error {
        if (t.ID != 0) {
            // query by ID, mutate the parameter, return nil
        }
        if (t.Tag != "") {
            // query by tag, mutate the parameter, return nil
        }
        return ErrWhatever
    }
usually I have some other package that defines all of the types that can appear on the wire (which I often call `wire` because `proto` is taken by protobuf), define some exported interface in that package with an unexported method so that no other packages can define new types for that interface, and then have a method on my db structs that returns the wire types, like this:

    func (t Task) Public() wire.Value {
        return wire.Task{
            // explicitly generate what you want
        }
    }


I prefer the same approach, but I'd create a separated struct, such as TaskFilter, and using a pointer because zero ID or empty tag can be valid values.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: