Documentation

Axara CLI

Speed Up your coding process with Smart Code Generator.


  • Doc for Version: 1.1.0
  • Author: Wirnat
  • Created: 9 October, 2022
  • Last Updated: 24 Maret, 2023

Axara CLI is code generator that used to build repeatable code depend on the Model and Template. For example, If you work using architecture layer this tool will useful to help build your CRUD(create update delete) on repository, delivery, or usecase layer, even mocking an service or swagger documentation. So you can just focus to the business logic on the project.


Installation

go install github.com/wirnat/axara@latest  

Quick Example

Let's practices with a simple case, assume we want to create a CRUD for modules company and customer, but only in repository layer.

First, prepare the models in model directory:

model/company.go
            
package model

type Company struct {
  BaseModel
  Name string `json:"name"` //@meta validate:true
}

//@Register Company
              
            
          
model/customer.go
          
package model

import "time"

type Customer struct {
  BaseModel
  Name      string     `json:"name"` //@meta validate:true
  BirthDate *time.Time `json:"birth_date"`
  Email     string     `json:"email"` //@meta validate:true
  Phone     string     `json:"phone"` //@meta validate:true
}

//@Register Customer            

model/base_model.go
            
package model

import (
	"time"
)

type BaseModel struct {
	ID        int64      `json:"id"`
	UUID      string     `json:"uuid"`
	CreatedAt time.Time  `json:"created_at"`
	UpdatedAt time.Time  `json:"updated_at"` //~ignore
	DeletedAt *time.Time `json:"deleted_at"` //~ignore
}

            
          

Model work as backbone in this generator, if @Register comment is found in model, it will execute the model to the generator.


Then, create a config yaml file simple_crud.yaml

You can use other name of config file if you want.

            
key: ᬅᬓ᭄ᬱᬭ
model_path: model
module_name: gox
models:
  Customer:
    module: customer
  Company:
    module: company

meta:
  result_path: modules
  repository_dir: ~result_path~/~module~/repository
  repository_gorm_dir: ~result_path~/~module~/repository/gorm_repository
  template: templates
  import_model: ~module_name~/~model_path~
  import_repository: ~module_name~/repository

module_traits:
  - name: "~model_snake~ repository"
    dir: ~repository_dir~
    template: ~template~/repository.text
    file_name: "~model_snake~.g.go"
    active: true

  - name: "~model_snake~ repository gorm"
    dir: ~repository_gorm_dir~
    template: ~template~/repository_gorm.text
    file_name: "~model_snake~_gorm.g.go"
    active: true


            
          
This file will work as a configurator, generator and aggregator, so you must assign:
key, original key (ᬅᬓ᭄ᬱᬭ)
model_path, model directory
module_name, the name of your project in go mod
meta, reusable variable that can used and decoded in config file or template file
models, declare model which will execute, and you can declare dynamic meta per model. in this example we add meta with key module and dynamic value
module_traits is a file generator, it will generate file base on the template which assign in template.
  name, is the name of action
  dir, the location of generated file (it will auto generate folder)
  file_name, the name of generated file
  meta, is a custom meta variable that can called in template.

Module Trait will executed when the generator read the model that has @Register [Model]

But how to use/decode Meta ?

Axara CLI has default meta and can called in generator config file or in template file, but you can also add meta in config file or you can add meta in models (dynamic meta)

In config file you can use ~meta name~ to make decode value to your config for more dynamic and readable.


Now let's create the template, at module_traits in config file we have declared 2 generate file, so we need create 2 template too.

Template for interface repository file, templates/repository.text :

                
package repository

import (
  "context"
  "{{.Meta.import_model}}"
  "time"
)

type {{.Model}} interface {
  Fetch(ctx context.Context, param Param) (res []model.{{.Model}}, err error)
  Show(ctx context.Context, param Param) (res model.{{.Model}}, err error)
  Update(ctx context.Context, param Param, update model.{{.Model}}) (err error)
  Store(ctx context.Context, store model.{{.Model}}) (err error)
  Delete(ctx context.Context, id int64) (err error)
}

type Param struct {
  {{- range $m:=.ModelFields }}
        {{$m.Name }} *{{$m.Type }} `json:"{{$m.Json -}}" form:"{{$m.Json }}" query:"{{$m.Json }}" {{if eq $m.Meta.validate "true"}}validate:"required"{{end}}`
    {{- end}}
}                  
                  

                
              

Template for repository implementation file, templates/repository_gorm.text:

                
package gorm_repository

import (
  "context"
  "gorm.io/gorm"
  "{{.Meta.import_model}}"
  "{{.Meta.import_repository}}"
)

type {{.ModelCamel}}Gorm struct {
  gorm.DB
}

func New{{.Model}}Gorm(DB gorm.DB) *{{.ModelCamel}}Gorm {
  return &{{.ModelCamel}}Gorm{DB: DB}
}

func filter(db *gorm.DB, param repository.Param) {
  {{- range $m:=.ModelFields }}
      if param.{{$m.Name}} != nil {
        *db = *db.Where("{{$m.Json}}=?", *param.{{$m.Name -}})
        }
        {{- end}}
}

func (r {{.ModelCamel}}Gorm) Fetch(ctx context.Context, param repository.Param) (res []model.{{.Model}}, err error) {
  filter(&r.DB, param)
  err = r.DB.Find(&res).Error
  return
}

func (r {{.ModelCamel}}Gorm) Show(ctx context.Context, param repository.Param) (res model.{{.Model}}, err error) {
  filter(&r.DB, param)
  err = r.DB.First(&res).Error
  return
}

func (r {{.ModelCamel}}Gorm) Update(ctx context.Context, param repository.Param, update model.{{.Model}}) (err error) {
  filter(&r.DB, param)
  err = r.DB.Updates(&update).Error
  return
}

func (r {{.ModelCamel}}Gorm) Store(ctx context.Context, store model.{{.Model}}) (err error) {
  err = r.DB.Create(&store).Error
  return
}

func (r {{.ModelCamel}}Gorm) Delete(ctx context.Context, id int64) (err error) {
  err = r.DB.Where("id=?", id).Delete(&model.{{.Model}}{}).Error
  return
}
                  

                
              
Double curly bracket will call variable which declared by default in generator such as {{.Model}}, config file variable, or meta that's setted in config file.

Execute the generator by following command,axara generate [your config file] --models [execute models]:


axara generate simple_crud.yaml --models Customer,Company  

The result is a complete set of folders with files formed by the template


How it's work?

Axara-cli works depend on template, model, and config file.
1. On start process generator will read config file.
2. Scan the model directory to find model that has @Register [model] in model file
3. Then module_traits in config file will execute template and, meta variable base on scanned model will decoded on this process.

Meta

Meta is a variable that used for config and template file. Meta can be set from model file or config file.

Model Meta

To add meta base on model, you can declare model and add key value of meta

            
models:
  Customer:
    module: customer
    anything: hello world!
  Company:
    module: company
            
          
Consider if you want to use variable base on model (scanned model turn), add meta from model is the best choice

Model Field Meta

By default, every fields of model has stored to model_fields meta and can be used in template file,
but you also can add meta to every field when template is getting complicated.

  
    type Branch struct {
      BaseModel
      CompanyID   int64   `json:"company_id"` //@meta validate:true
      Name        string  `json:"name"` //@meta validate:true
      Description *string `json:"description"`
    }
  
Static Meta

You also can add meta from config file, by adding in meta, but keep in your mind meta must be unique

             
meta:
  result_path: modules
  repository_dir: ~result_path~/~module~/repository
  repository_gorm_dir: ~result_path~/~module~/repository/gorm_repository
  template: templates
  import_model: ~module_name~/~model_path~
  import_repository: ~module_name~/repository
             
           
Meta Usage

In config file you can call meta using ~[meta-name]~, in template you can call meta using {{.[meta-name]}}

Default Meta By Default, axara cli already generated meta for common use:
  • Model - Name of scanned model
  • ModelFields - List of model fields, every field contains:
    • Json - string
    • Name - string
    • Type - string
    • IsPtr - bool
    • Meta - map[string]interface{}
  • ModuleName - Declared Module Name
  • ResultPath - Declared result path
  • ModelPath - Declared model path

Template

Main former of this generator is The Template, if config file serve as directory and architecture builder, template is the code builder. Template is compiled when all meta, variable, and config in json and model file has been compiled.

Template will be compiled using default template from Template Go, so you can use directive, variable call, and all feature of The Golang Template.

Here some template examples:

Loop and meta:
              
package {{.Meta.service}}_gorm

import (
	"{{.ModuleName}}/{{.ResultPath}}/{{.Meta.service}}/{{.Meta.service}}_request"
	"gorm.io/gorm"
)

func filter(db *gorm.DB, param {{.Meta.service}}_request.{{.Model}}Param) {
	{{- range $m:=.ModelFields }}
	if param.{{$m.Name}} != nil {
    db = db.Where("{{$m.Name}}", *param.{{$m.Name -}})
    }
    {{- end}}
}