Gorm Draft
This commit is contained in:
parent
5bd8eef77d
commit
e24a223ee1
@ -46,3 +46,128 @@ Being that I come from the Django (and a few years of ActiveRecord) land, I reac
|
|||||||
If you want to skip directly to the source, check out `https://gitea.tyrel.dev/tyrel/go-webservice-gin <https://gitea.tyrel.dev/tyrel/go-webservice-gin>`_.
|
If you want to skip directly to the source, check out `https://gitea.tyrel.dev/tyrel/go-webservice-gin <https://gitea.tyrel.dev/tyrel/go-webservice-gin>`_.
|
||||||
Full design disclosure: I followed a couple of blog posts in order to develop this, so it is in the form explictly decided upon by the `logrocket blog post <https://blog.logrocket.com/how-to-build-a-rest-api-with-golang-using-gin-and-gorm/>`_ and may not be the most efficient way to organize the module.
|
Full design disclosure: I followed a couple of blog posts in order to develop this, so it is in the form explictly decided upon by the `logrocket blog post <https://blog.logrocket.com/how-to-build-a-rest-api-with-golang-using-gin-and-gorm/>`_ and may not be the most efficient way to organize the module.
|
||||||
|
|
||||||
|
In order to instantiate a model definition, it's pretty easy.
|
||||||
|
What I did is make a new package called ``models`` and inside made a file for my Album.
|
||||||
|
|
||||||
|
.. code:: go
|
||||||
|
|
||||||
|
type Album struct {
|
||||||
|
ID string `json:"id" gorm:"primary_key"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Artist string `json:"artist"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
This tracks with how I would do the same for any other kind of struct in Go, so this wasn't too difficult to do.
|
||||||
|
What was kind of annoying was that I had to also make some structs for Creating the album and Updating the Album, this felt like duplicated effort that might have been better served with some composition.
|
||||||
|
|
||||||
|
I would have structured the controllers differently, but that may be a Gin thing and how it takes points to functions, vs pointers to receivers on a struct.
|
||||||
|
Not specific to GORM.
|
||||||
|
Each of the controller functions were bound to a ``gin.Context`` pointer, rather than receivers on an AlbumController struct.
|
||||||
|
|
||||||
|
The ``FindAlbum`` controller was simple:
|
||||||
|
|
||||||
|
.. code:: go
|
||||||
|
|
||||||
|
func FindAlbum(c *gin.Context) {
|
||||||
|
var album models.Album
|
||||||
|
if err := models.DB.Where("id = ?", c.Param("id")).First(&album).Error; err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{"data": album})
|
||||||
|
}
|
||||||
|
|
||||||
|
Which will take in a ``/:id`` path parameter, and the GORM part of this is the third line there.
|
||||||
|
|
||||||
|
.. code:: go
|
||||||
|
|
||||||
|
models.DB.Where("id = ?", c.Param("id")).First(&album).Error
|
||||||
|
|
||||||
|
To run a select, you chain a ``Where`` on the DB (which is the connection here) and it will build up your query.
|
||||||
|
If you want to do joins, this is where you would chain ``.Joins`` etc...
|
||||||
|
You then pass in your album variable to bind the result to the struct, and if there's no errors, you continue on with the bound variable.
|
||||||
|
Error handling is standard Go logic, ``if err != nil`` etc and then pass that into your API of choice (Gin here) error handler.
|
||||||
|
|
||||||
|
This was really easy to set up, and if you want to get a slice back you just use ``DB.Find`` instead, and bind to a slice of those structs.
|
||||||
|
|
||||||
|
.. code:: go
|
||||||
|
|
||||||
|
var albums []models.Album
|
||||||
|
models.DB.Find(&albums)
|
||||||
|
|
||||||
|
|
||||||
|
SQLX
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
SQLX is a bit different, as it's not an ORM, it's extensions in Go to query with SQL, but still a good pattern for abstracting away your SQL to some dark corner of the app and not inline everywhere.
|
||||||
|
For this I didn't follow someone's blog post — I had a grasp on how to use Gin pretty okay by now and essentially copied someone elses repo with my existing model.
|
||||||
|
`gin-sqlx-crud <https://github.com/wetterj/gin-sqlx-crud>`_.
|
||||||
|
|
||||||
|
This one set up a bit wider of a structure, with deeper nested packages.
|
||||||
|
Inside my ``internal`` folder there's ``controllers``, ``forms``, ``models/sql``, and ``server``.
|
||||||
|
I'll only bother describing the ``models`` package here, as thats the SQLX part of it.
|
||||||
|
|
||||||
|
In the ``models/album.go`` file, there's your standard struct here, but this time its bound to ``db`` not ``json``, I didn't look too deep yet but I presume that also forces the columns to set the json name.
|
||||||
|
|
||||||
|
.. code:: go
|
||||||
|
|
||||||
|
type Album struct {
|
||||||
|
ID int64 `db:"id"`
|
||||||
|
Title string `db:"title"`
|
||||||
|
Artist string `db:"artist"`
|
||||||
|
Price float64 `db:"price"`
|
||||||
|
}
|
||||||
|
|
||||||
|
An interface to make a service, and a receiver are made for applying the ``CreateAlbum`` form (in another package) which sets the form name and json name in it.
|
||||||
|
|
||||||
|
.. code:: go
|
||||||
|
|
||||||
|
func (a *Album) ApplyForm(form *forms.CreateAlbum) {
|
||||||
|
a.ID = *form.ID
|
||||||
|
a.Title = *form.Title
|
||||||
|
a.Artist = *form.Artist
|
||||||
|
a.Price = *form.Price
|
||||||
|
}
|
||||||
|
|
||||||
|
So there's the receiver action I wanted at least!
|
||||||
|
|
||||||
|
Nested inside the ``models/sql/album.go`` file and package, is all of the Receiver code for the service.
|
||||||
|
I'll just comment the smallest one, as that gets my point across.
|
||||||
|
Here is where the main part of GORM/SQLX differ - raw SQL shows up.
|
||||||
|
|
||||||
|
.. code:: go
|
||||||
|
|
||||||
|
func (s *AlbumService) GetAll() (*[]models2.Album, error) {
|
||||||
|
q := `SELECT * FROM albums;`
|
||||||
|
|
||||||
|
var output []models2.Album
|
||||||
|
err := s.conn.Select(&output, q)
|
||||||
|
// Replace the SQL error with our own error type.
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, models2.ErrNotFound
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &output, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
This will return a slice of Albums - but if you notice on the second line, you have to write your own queries.
|
||||||
|
A little bit more in control of how things happen, with a ``SELECT * ...`` vs the gorm ``DB.Find`` style.
|
||||||
|
|
||||||
|
To me this feels more like using ``pymysql``, in fact its a very similar process.
|
||||||
|
You use the ``service.connection.Get`` and pass in what you want the output bound to, the string query, and any parameters.
|
||||||
|
This feels kind of backwards to me - I'd much rather have the order be: query, bound, parameters, but thats what they decided for their order.
|
||||||
|
|
||||||
|
Conclusion
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Overall, both were pretty easy to set up for one model.
|
||||||
|
Given the choice I would look at who the source code is written for.
|
||||||
|
If you're someone who knows a lot of SQL, then SQLX is fine.
|
||||||
|
If you like abstractions, and more of a "Code as Query" style, then GORM is probably the best of these two options.
|
||||||
|
|
||||||
|
I will point out that GORM does more than just "query and insert" there is migration, logging, locking, dry run mode, and more.
|
||||||
|
If you want to have a full fledged system, that might be a little heavy, then GORM is the right choice.
|
||||||
|
|
||||||
|
SQLX is great if what you care about is marshalling, and a very quick integration into any existing codebase.
|
||||||
|
Loading…
Reference in New Issue
Block a user