Tutorial - Fundamentals
What you’ll learn
- How to create a basic Granitic application
- Some of Granitic’s fundamental principles
- How to create a simple web service
Prerequisites
- Follow the Granitic installation instructions
- Read the before you start tutorial
- Have access to a text editor or IDE for writing Go and JSON files and a terminal emulator or command prompt (referred to as a terminal from now on)
Note for Windows users
Refer to the Windows notes in the before you start tutorial
Introduction
This tutorial will show you how to create a simple Granitic application that exposes a JSON web service over HTTP. It will also introduce you to some of Granitic’s fundamentals.
Creating a Grantic project
A Granitic project is made up of three sets of assets:
- Go source files
- JSON application configuration files
- JSON component definition files
Each of these types of asset will be explained over the course of this tutorial. Granitic includes a command line tool to create a skeleton project that can be compiled and started.
Run the following in a terminal:
cd ~ grnc-project recordstore
This will create the following files under your home directory:
recordstore .gitignore main.go go.mod comp-def/ common.json config/ base.json
Starting and stopping your application
This minimal configuration is actually a working Granitic application that can be started and stopped - it just doesn’t do anything interesting.
Start the application by returning to your terminal and running
cd recordstore go mod tidy grnc-bind && go build ./recordstore
You should see output similar to:
06/Aug/2023:15:16:28 Z INFO [grncInit] Granitic v2.2.2 06/Aug/2023:15:16:28 Z INFO [grncInit] Starting components 06/Aug/2023:15:16:28 Z INFO [grncInit] Ready (startup time 2.436292ms)
This means your application has started and is waiting. You can stop it with CTRL+C
and will see output similar to
06/Aug/2023:15:16:32 Z INFO [grncInit] Shutting down (system signal)
Facilities
A facility
is Granitic’s name for a high-level feature that your application can enable or disable. By default,
most of the features are disabled. You can see which features are available to your applications and whether or not they’re enabled
by inspecting the file facility/config/active.json
in your Granitic installation folder or here on GitHub
{
"Facilities": {
"HTTPServer": false,
"JSONWs": false,
"XMLWs": false,
"FrameworkLogging": true,
"ApplicationLogging": true,
"QueryManager": false,
"RdbmsAccess": false,
"ServiceErrorManager": false,
"RuntimeCtl": false,
"TaskScheduler": false
}
}
In order to build a JSON web service that will listen for and handle HTTP requests, you will need to enable two facilities:
HTTPServer
and JSONWs
(JSON Web Services).
We do this by overriding the default setting for each facility. To do this, open the JSON config/base.json
that was generated for you and change it so that it looks like:
{
"Facilities": {
"HTTPServer": true,
"JSONWs": true
}
}
(from now on this file will be referred to as your base config file)
If you return to your terminal and run:
grnc-bind && go build && ./recordstore
You’ll see an additional line of logging on startup similar to:
06/Aug/2023:15:20:45 Z INFO [grncHTTPServer] Listening on 8080
Which shows that an HTTP server is listening for web service requests on the default port of 8080. Stop the running
service with CTRL+C
Adding an endpoint
An endpoint
is Granitic’s preferred name for code that handles a web service request to a particular URI pattern for a
particular HTTP method (GET, POST etc). Most of the mechanics of routing a request to your code and converting between
JSON and your custom Go code is handled by Granitic, you will be concerned mainly with defining your endpoint logic.
Endpoint logic is code in a Go struct that has a member function that Granitic can call to pass control of a request once the framework has completed its automatic steps.
Create the file artist/get.go
in your recordstore
project and set the contents to:
package artist
import (
"github.com/graniticio/granitic/v2/ws"
"context"
)
type GetLogic struct {}
func (gl *GetLogic) Process(ctx context.Context, req *ws.Request, res *ws.Response) {
res.Body = Info{
Name: "Hello, World!",
}
}
type Info struct {
Name string
}
This code defines an object implementing the ws.WsRequestProcessor interface and another object that will be used to store the results of the web service call, in this case a recording artist with the unlikely name “Hello, World!”
Turning your code into a component
At the core of Granitic is an Inversion of Control (IoC) container, sometimes also called a Dependency Injection framework. Granitic looks after the lifecycle (creating and destroying) of the Go objects you define, but needs to be told which objects should be included in your application and how they should be configured.
These definitions are stored in JSON component definition files which, by default, are stored in your project in a folder called comp-def
.
You can have as many files as you like in this folder, and it is recommended that you group related components in to separate
named files.
Open the file comp-def/common.json
and set the content to:
{
"packages": [
"github.com/graniticio/granitic/v2/ws/handler",
"recordstore/artist"
],
"components": {
"artistHandler": {
"type": "handler.WsHandler",
"HTTPMethod": "GET",
"Logic": {
"type": "artist.GetLogic"
},
"PathPattern": "^/artist"
}
}
}
A component definition file has two sections. The packages
section declares the Go packages containing the
code that you intend to use as components. The components
section declares uniquely named components that
you want to be managed by Granitic.
The sole component in this file, artistHandler
, is an instance of handler.WsHandler, a built-in Granitic
type. A handler.WsHandler
coordinates the bulk of the request processing lifecycle as well as managing error-handling
for a web service request.
One of the fields on this component, Logic
, expects another component to be injected into to it. In this case
we’re defining the component that the needs to be injected inline as a nested component. Note that the nested component
doesn’t need a name, just a type - this case the artist.GetLogic
struct we defined above.
The minimal configuration in this example specifies the HTTP method that the handler will respond to (GET) and a regex for matching against incoming request paths.
Binding
Granitic is written in Go because of Go’s positioning between C’s performance and memory consumption and the relative ease-of-use of JVM and CLR languages (Java, C# etc). One of the compromises we accept in using Go is that it is a statically-linked language with no facility for creating objects from a text name at runtime - if you want to use a type in your application, it must be referenced at compile time.
In order to reconcile the configuration-based approach favoured by Granitic with this limitation, a tool is used to generate Go source code from component definition files.
Return to your terminal and run
grnc-bind
You will notice that a Go source file bindings/bindings.go
has been created. You will not
(and in fact should not) edit this file directly, but feel free to examine it to see what is happening.
You will need to re-run grnc-bind
whenever you change your component definition file - i.e. the files under your
comp-def folder
Building and testing your application
Every Go application requires an entry point main
method. For a Go application that was created using the
grnc-project
tool, the main
method is in the main.go
file at the root of the
project. For this tutorial, this file will look like:
package main
import "github.com/graniticio/granitic/v2"
import "recordstore/bindings"
func main() {
granitic.StartGranitic(bindings.Components())
}
This simply takes the objects generated by grnc-bind
and passes them to Granitic. For the vast majority of
Granitic applications you will not need to modify or even look at this file.
Return to your terminal and run:
grnc-bind && go build && ./recordstore
Now open a browser and visit:
http://localhost:8080/artist or http://[::1]:8080/artist
and you should see the response:
{
"Name": "Hello, World!"
}
Recap
- Granitic applications contain Go source files, configuration files and component definition files
- The
grnc-project
tool can create an empty, working Granitic application - Components are a named instance of a Go object, managed by Granitic’s IoC container
- The
grnc-bind
tool converts your component definition files into Go source - run the tool whenever you change your component definitions.
Next
The next tutorial covers configuration in more depth