Generated 'models' Folder

The ‘models’ Folder

FirstApp
├── appobj
│   ├── appconf.go
│   ├── appobj.go
|   └── lead_set_get.go
.
.
.
├── models
│   ├── authm.go
│   ├── errors.go
│   ├── group_authm.go
│   ├── modelfuncs.go
│   ├── personm_ext.go
│   ├── personm.go
│   ├── servicesm.go
│   ├── usr_groupm.go
│   ├── usrm.go
│   └── ext
│       └── model_ext_interfaces.go
├── util
│   └── strings.go
├── .dev.config.json
├── .prd.config.json
├── main_test.go
└── main.go

A model is created for each entity that has been modeled in the my_model.json files, as well as well as the static models used to support users and authorizations.

Models define an entity’s structure and member field characteristics such as type, required/not-required, db-type etc. Each model has a corresponding controller that examines the request, parses the incoming JSON data into the model structure, and then calls the appropriate method in the entity-model based on the end-point / http method. The model provides a validator, which can be used to perform detailed checks and normalizations on the entity data prior to making the call to the sqac ORM.

Empty model validations are generated for each entity field, and are designed to be extended by the application developer. Validation methods are generated for each entity field and added to the model’s entity validator. For example, the model source file for entity ‘Library’ (./models/librarym.go), contains a ‘libraryValidator’ type. Validation methods for each of the library entity’s fields are attached to this type.

The validator type also contains methods matching the public interface (LibraryDB) of the model’s service definition. The model’s service declaration includes a validator member, and due to the manner of the declaration, it is the validator that is passed back to the caller (controller) when model access is needed.

    // newLibraryValidator returns a new libraryValidator
    func newLibraryValidator(ldb LibraryDB) *libraryValidator {
        return &libraryValidator{
        LibraryDB: ldb,
        }
    }

    // NewLibraryService declaration
    func NewLibraryService(handle sqac.PublicDB) LibraryService {

        ls := &librarySqac{handle}

        lv := newLibraryValidator(ls) // *db
        return &libraryService{
            LibraryDB: lv,
        }
    }

In the NewLibraryService function, see that two members are declared:

  • ls contains an implementation of the generated LibraryDB interface which is used to call the ORM layer following successful execution of the model’s validations
  • lv contains an implementation of the generated LibraryDB interface, as well as the set of empty generated field validation methods

Using the creation of a new ‘Library’ entity as an example, the controller will parse the JSON body of the incoming request into a ‘Library’ entity struct. The controller will then call the entity’s model.Create method. The ‘libraryValidator.Create’ method (on lv) will execute the implemented field validations, then call the service’s model.Create() method (on ls)which will in-turn make the required call to the ORM.

    // Create validates and normalizes data used in the library creation.
    // Create then calls the creation code contained in LibraryService.
    func (lv *libraryValidator) Create(library *Library) error {

        // perform normalization and validation -- comment out checks that are not
        // required note that the check calls are generated as a straight enumeration
        // of the entity structure.  It may be neccessary to adjust the calling order
        // depending on the relationships between the fields in the entity structure.
        err := runLibraryValFuncs(library,
            lv.normvalName,
            lv.normvalCity,
        )

        if err != nil {
            return err
        }

        // use method-chaining to call the library service Create method
        return lv.LibraryDB.Create(library)
    }

The last line of the method is the most interesting, as it demonstrates something known as method-chaining which allows the call to implicitly access the ‘ls’ methods. Look carefully at the code in this area so you understand what is happening, and perhaps lookup ‘method-chaining’ as it pertains to golang.

Note that at the moment, validations are intended to be coded directly in the body of the generated model code even though model extension points are available. This is in contrast with the extension-point technique implemented in the controller and at the sqacService level in the model file (see Extension Points). The reasons for this are as follows:

  • It is expected that no validations will be coded until the model has been stabilized.
  • It is generally desirable to get an application working (or mostly working), then start worrying about validations.
  • Extension points exist as a convenience in the case where data needs pre/post processing.
  • For most entities, some sort of validation will be required on the majority of fields. We treat the validations as first-class citizens in the application rather than extension-points.
  • By treating validations as first-class citizens we do not need to use type assertion and reflection in the validation layer when performing the checks. This is in contrast to the model extension-point interfaces.
  • If there is a concern regarding the over-writing of coded validations due to application regeneration, it is simple for an application developer to implement their own sub-package with methods or functions containing the check code. Jiffy application generation will not overwrite files that it is not responsible for during a regeneration of an application.

By default, a CRUD interface is generated for each entity. Using the ‘Library’ example, the generated code for the CRUD end-points look as follows:

// ====================== Library protected routes for standard CRUD access ======================
a.router.HandleFunc("/librarys", requireUserMw.ApplyFn(a.libraryC.GetLibrarys)).Methods("GET").Name("library.GET_SET")
a.router.HandleFunc("/library", requireUserMw.ApplyFn(a.libraryC.Create)).Methods("POST").Name("library.CREATE")
a.router.HandleFunc("/library/{id:[0-9]+}", requireUserMw.ApplyFn(a.libraryC.Get)).Methods("GET").Name("library.GET_ID")
a.router.HandleFunc("/library/{id:[0-9]+}", requireUserMw.ApplyFn(a.libraryC.Update)).Methods("PUT").Name("library.UPDATE")
a.router.HandleFunc("/library/{id:[0-9]+}", requireUserMw.ApplyFn(a.libraryC.Delete)).Methods("DELETE").Name("library.DELETE")

The generated go struct for the ‘Library’ model looks as follows:

    // Library structure
    type Library struct {
      ID   uint64 `json:"id" db:"id" sqac:"primary_key:inc"`
      Href string `json:"href" db:"href" sqac:"-"`
      Name string `json:"name" db:"name" sqac:"nullable:false;index:non-unique;index:idx_library_name_city"`
      City string `json:"city" db:"city" sqac:"nullable:false;index:idx_library_name_city"`
    }

The model structure and tags are explained:

Field Name Description
ID This is the injected key for the entity. The sqac tag “primary_key:inc” instructs the ORM that this field is to be created as an auto-incrementing column in the backend DBMS.
Href Each entity has an Href field injected into its structure when the application is generated. The Href value provides a direct link to read, update or delete the represented entity. This can be useful if the entity was returned as part of a list, or via a relation-based request. Changes to entities must be carried out via the direct links rather than through relation-type requests. Enforcement of this precludes the requirement of coding / executing additional checks during updates to make sure that the relationship path is valid. Authorization for end-point access is also simplified via this model. Sqac tag “-” indicates that this field is not persisted on the database and is not included in the table schema.
Name Name is a field from the model file, and has the following attributes in the backend DBMS based on the sqac tag-values: Not nullable, has a non-unique btree index, is part of a composite (non-unique) index consisting of the ‘name’ and ‘city’ table columns.
City City is a field from the model file, and has the following attributes in the backend DBMS based on the sqac tag-values: Not nullable, is part of a composite (non-unique) index consisting of the ‘name’ and ‘city’ table columns.

For a more complete explanation of the Sqac ORM tags and operation, see the examples contained in README.md of the sqac library at: https://github.com/1414C/sqac or the sqac ORM documentation.

The models folder also contains an ‘ext’ sub-directory which is used to hold the interface definitions for model extension-points if you wish to use them. See the Extension Points section of this document for more details.