Skip to content

Getting Started

This guide walks you through installing Fountain, bootstrapping your first application, and running it. Fountain organizes systems as App → Services → Servers (Pods): an App is the top-level process, it hosts one or more Services, and each Service exposes one or more protocol Servers.

Prerequisites

  • Go 1.25.4 or newer — Fountain targets the go 1.25.4 directive declared in its go.mod.
  • Access to the private GitLab module — Fountain is distributed as the private module gitlab.soludian.com/soludian/fountain, so the Go toolchain must be told to treat it as private and given a way to resolve it.

Mark the module namespace as private so the go command does not try to fetch it through the public proxy or checksum database:

bash
go env -w GOPRIVATE=gitlab.soludian.com/*

If you cannot resolve the module directly, clone it and point your module at the local copy with a replace directive:

bash
git clone gitlab.soludian.com/soludian/fountain
go mod init ${PROJECT_PATH}
go mod edit -replace=gitlab.soludian.com/soludian/fountain=./fountain

Install

With the private-module setup above in place, add Fountain to your module:

bash
go get gitlab.soludian.com/soludian/fountain

Because this is a private module, the GOPRIVATE step (or the go mod edit -replace approach) is required first — otherwise go get will fail trying to reach the public module proxy.

Your first app

A Fountain program is built from AppInstance components that the framework wires up and runs for you. You construct the application with fountain.New() (or the package-level fountain.WithAppInstances(...) shortcut), register your instances, and call Serving() to start the lifecycle. Serving() blocks: Fountain initializes each instance, starts its servers, and keeps the process alive until a shutdown signal arrives.

The example below defines an APIServer that embeds an fhttp.Server. In Initialize() it installs the HTTP server from the server.fhttp configuration key, wires up core controllers and crypto instances, then registers a handful of routes — including encrypted responses via fhttp.WithContext(ctx).WithEncryption(). main() simply hands the instance to Fountain and calls Serving().

go
/* !!
 * File: main.go
 * File Created: Friday, 28th October 2022 5:19:38 pm
 * Author: Kim Ericko ([email protected])
 * -----
 * Last Modified: Wednesday, 8th March 2023 4:40:18 pm
 * Modified By: Kim Ericko ([email protected])
 * -----
 * Copyright 2022 Soludian, soludian.com
 * All rights reserved.
 *
 * Licensed under the SOLUDIAN TECHNOLOGY SOLUTION CO., LTD Software License Agreement.
 * Unauthorized use, reproduction, or distribution is prohibited (the "License");
 *
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 * [email protected] / https://www.soludian.com/license
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * -----
 *
 * HISTORY:
 *
 * Date      	By	Comments
 * ----------	---	---------------------------------------------------------
 */

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/gofiber/fiber/v3"
	"gitlab.soludian.com/soludian/fountain"
	"gitlab.soludian.com/soludian/fountain/biz/core"
	"gitlab.soludian.com/soludian/fountain/libs/base/lib_3rd"
	"gitlab.soludian.com/soludian/fountain/libs/crypto"
	"gitlab.soludian.com/soludian/fountain/libs/flog"
	"gitlab.soludian.com/soludian/fountain/libs/fnet/fhttp"
)

var logger = flog.NewFountainLoggerOnce()

type NoModule struct {
}

func (NoModule) Liveness() bool {
	return true
}

func (NoModule) Readiness() bool {
	return false
}

type APIServer struct {
	*fhttp.Server
}

func (s *APIServer) Initialize() error {
	s.Server = fhttp.WithConfigKey("server.fhttp").InstallFountainInstance()

	core.InstallCoreControllers()

	cr := crypto.InstallFountainInstances()
	log.Printf("Initializing: %+v", cr.GetFountainInstanceNames())

	s.Server.Get("/panic", func(ctx fiber.Ctx) error {
		err := fmt.Errorf("route panic")
		logger.WErr(err).Panicf(err.Error())
		return err
	})

	s.Server.Get("/200", func(ctx fiber.Ctx) error {
		return ctx.SendString("hello")
	})

	s.Server.Get("/hello", func(ctx fiber.Ctx) error {
		logger.Infof(ctx.Route().Path)
		return ctx.JSON("Hello client: " + ctx.Get("app"))
	})

	s.Server.Get("/encrypt", func(ctx fiber.Ctx) error {
		logger.Infof(ctx.Route().Path)
		return fhttp.WithContext(ctx).WithEncryption().WithStatus(200).WriteSuccess("Hello client: " + ctx.Get("app"))
	})

	s.Server.Get("/encrypt-cbc", func(ctx fiber.Ctx) error {
		data := make(map[string]any)
		data["server"] = "encrypt-cbc"
		data["time"] = time.Now().String()

		logger.Infof(ctx.Route().Path)
		return fhttp.WithContext(ctx).WithEncryptionFunc(crypto.GetFountainInstance("AES-CBC")).WithStatus(200).WriteSuccess(data)
	})

	s.Server.Get("/encrypt-ctr", func(ctx fiber.Ctx) error {
		data := make(map[string]any)
		data["server"] = "encrypt-ctr"
		data["time"] = time.Now().String()

		logger.Infof(ctx.Route().Path)
		return fhttp.WithContext(ctx).WithEncryptionFunc(crypto.GetFountainInstance("AES-CTR")).WithStatus(200).WriteSuccess(data)
	})

	s.Server.Get("/500", func(ctx fiber.Ctx) error {
		return ctx.Status(500).JSON("Hello client: " + ctx.Get("app"))
	})

	lib_3rd.RegisterHealthCheck("NoModule", &NoModule{})
	s.Server.InitHealthCheckPath("/")

	return nil
}

func main() {
	server := &APIServer{}
	fountain.WithAppInstances(server).Serving()
}

Configuration

Fountain reads its configuration from a config.yaml file using nested keys. Each library binds to a key — for example the HTTP server in the example above is installed with fhttp.WithConfigKey("server.fhttp"), so its settings live under the server.fhttp block. Any value can be overridden at runtime through environment variables using the FOUNTAIN_ prefix; an env var maps onto the equivalent nested config path, which is convenient for per-environment deployments and secrets.

yaml
env:
  log_print_level: 5
  log_file_level: 5
  
server.fhttp:
  addr: ":9007"
  enable_csrf: true
  enable_access_interceptor: true
  enable_access_interceptor_req: true
  enable_access_interceptor_res: true
  access_interceptor_req_res_filter: 'hahaha'
  server_read_timeout: 1m
  server_read_header_timeout: 1h
  server_write_timeout: "30m"
  server_http_timeout: "5s"
  metrics_enable: true

trace:
  service_name: "server"

crypto:
  - algorithm: AES-CTR
    key: "1234567890123456"
  - algorithm: AES-CBC
    key: "1234567890123456"

Run it

From the directory containing your main.go and config.yaml, start the app:

bash
go run .

Fountain initializes the registered instances and starts the HTTP server on the address from server.fhttp.addr (:9007 in the example). Once it is up you can reach the routes the example defines, such as GET /200, GET /hello, and the encrypted GET /encrypt. The process keeps running until you interrupt it (Ctrl+C), at which point Fountain shuts the instances down gracefully.

Next steps

  • Core Concepts — the App → Services → Servers model, lifecycle, and controllers in depth.
  • Building a Service — assemble a real service from Fountain components.