Golang、Docker和Gradle的项目结构

huangapple go评论74阅读模式
英文:

Project structure for golang, docker, gradle

问题

我刚开始学习Go(golang),想为一个将使用Gradle构建并部署到Docker镜像的项目设置一个新的文件夹结构。我很难确定这个项目的结构可能是什么样的,主要是因为GOPATH结构和Go语言工具似乎与使用Gradle或配置可以克隆(Git)的项目相抵触。

该项目最终将包含用Go编写的各种服务器端代码,以及用HTML和JavaScript编写的客户端代码,因此我需要一个适用于Gradle构建和打包所有这些组件的项目结构。

有没有人对此有一个良好的工作结构和工具推荐?

英文:

I am just starting with Go (golang) and want to get a new project folder structure set up for a project that will be built with Gradle and deployed to a Docker image. I'm struggling to determine what this project structure might look like, primarily because of the GOPATH structure and the fact that the Go language tooling seems to be antithetical to using Gradle or to configuring a project that can be cloned (Git).

The project will eventually contain various server-side code written in Go, client side code written in HTML and JavaScript, so I need a project structure that works well for Gradle to build and package all of these kinds of pieces.

Does anyone have a good working structure and tooling recommendations for this?

答案1

得分: 3

当我开始使用Go语言时,我尝试了各种构建工具,从mavengulp

结果证明,至少对我来说,它们带来的伤害大于好处,所以我开始使用Go的看似不起眼但实际上非常精心设计的功能之一,即go generate。可以添加简单的shell脚本或偶尔使用Makefile进行自动化。

示例项目

我创建了一个示例项目来更清楚地说明这一点

/Users/you/go/src/bitbucket.org/you/hello/
├── Dockerfile
├── Makefile
├── _templates
│   └── main.html
└── main.go

main.go

这是一个简单的Web服务器,使用嵌入到二进制文件中的模板来提供"Hello, World!"。使用了优秀的go.rice工具:

//go:generate rice embed-go

package main

import (
    "html/template"
    "log"
    "net/http"

    rice "github.com/GeertJohan/go.rice"
)

func main() {

    templateBox, err := rice.FindBox("_templates")
    if err != nil {
        log.Fatal(err)
    }
    // 获取文件内容作为字符串
    templateString, err := templateBox.String("main.html")
    if err != nil {
        log.Fatal(err)
    }
    // 解析并执行模板
    tmplMessage, err := template.New("message").Parse(templateString)
    if err != nil {
        log.Fatal(err)
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if err := tmplMessage.Execute(w, map[string]string{"Greeting": "Hello, world!"}); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })

    log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}

注意这一行

//go:generate rice embed-go

当你调用go generate时,你的源文件将被扫描以查找这样的行,并执行相应的命令。在这种情况下,将生成一个名为rice-embed.go的文件,你的目录将如下所示:

/Users/you/go/src/bitbucket.org/you/hello/
├── Dockerfile
├── Makefile
├── _templates
│   └── main.html
├── main.go
└── rice-box.go

你可以在//go generate中调用webpack,例如将你的内容组合在一起,然后再生成一个rice-box.go。这样,你的所有内容都将嵌入到二进制文件中,部署将变得轻而易举。

Dockerfile

我在这个示例中使用了一个相当简单的Dockerfile:

FROM alpine:latest
MAINTAINER You <you@example.com>
COPY hello /usr/bin
EXPOSE 8080
CMD ["/usr/bin/hello"]

然而,这带来了一个问题:我们不能使用go:generate来生成Docker镜像,因为在我们需要调用go:generate时,新的二进制文件还没有构建出来。这将导致我们做一些丑陋的事情,比如

go generate && go build && go generate

导致Docker镜像构建两次等等。因此,我们需要一个不同的解决方案。

解决方案A:一个shell脚本

我们当然可以想出这样的解决方案:

#!/bin/bash
# 省略了存在性检查
GO=$(which go)
DOCKER=$(which docker)
$GO generate
$GO test
$GO build
$DOCKER -t you/hello .

然而,这会带来一个问题:你将始终使用shell脚本执行整个序列。即使你只想运行测试,你最终也会构建Docker镜像。随着时间的推移,这种情况会累积。在这种情况下,我倾向于使用

解决方案B:一个Makefile

Makefile是GNU make的配置文件。

CC = $(shell which go 2>/dev/null)
DOCKER = $(shell which docker 2>/dev/null)

ifeq ($(CC),)
$(error "go is not in your system PATH")
else
$(info "go found")
endif

ifeq ($(DOCKER),)
$(error "docker not in your system path")
else
$(info "docker found")
endif

.PHONY: clean generate tests docker all

all: clean generate tests hello docker

clean:
    $(RM) hello rice-box.go cover.out
generate:
    $(CC) generate
tests: generate
    $(CC) test -coverprofile=cover.out
hello: tests
    $(CC) build
docker: hello
    $(DOCKER) build -t sosample/hello .

对于这个问题来说,完整的解释超出了本回答的范围,但基本上你可以在这里调用make,并且all目标将被构建:旧构建的文件将被删除(clean),生成一个新的rice-box.gogenerate),依此类推。但是,如果你只想运行测试,只需调用make test,它只会执行cleangeneratetests目标。

英文:

When I started with Go, I fiddled with a rather wide variety of build tools, from maven to gulp.

It turned out that at least for me, they were doing more harm than good, so I started to use Go's seemingly unimposing, but really well thought out features. One of them isgo generate. Add simple shell scripts or occasionally Makefiles for automation.

Sample project

I have put together a sample project to make this more clear

/Users/you/go/src/bitbucket.org/you/hello/
├── Dockerfile
├── Makefile
├── _templates
│&#160;&#160; └── main.html
└── main.go

main.go

This is a simple web server which serves "Hello, World!" using a template which is embedded into the binary using the excellent go.rice tool:

//go:generate rice embed-go

package main

import (
	&quot;html/template&quot;
	&quot;log&quot;
	&quot;net/http&quot;

	rice &quot;github.com/GeertJohan/go.rice&quot;
)

func main() {

	templateBox, err := rice.FindBox(&quot;_templates&quot;)
	if err != nil {
		log.Fatal(err)
	}
	// get file contents as string
	templateString, err := templateBox.String(&quot;main.html&quot;)
	if err != nil {
		log.Fatal(err)
	}
	// parse and execute the template
	tmplMessage, err := template.New(&quot;message&quot;).Parse(templateString)
	if err != nil {
		log.Fatal(err)
	}

	http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
		if err := tmplMessage.Execute(w, map[string]string{&quot;Greeting&quot;: &quot;Hello, world!&quot;}); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	})

	log.Fatal(http.ListenAndServe(&quot;127.0.0.1:8080&quot;, nil))
}

Note the line

//go:generate rice embed-go

When you call go generate your source file will be scanned for such lines and the according commands will be executed. In this case, a file called rice-embed.go will be generated and your directory will look like this:

/Users/you/go/src/bitbucket.org/you/hello/
├── Dockerfile
├── Makefile
├── _templates
│&#160;&#160; └── main.html
├── main.go
└── rice-box.go

You could call webpack in a //go generate for example, to get your stuff together and another generate to create a rice-box.go from the result. This way, all your stuff would be embedded in your binary and would become a breeze to deploy.

Dockerfile

I have used a rather simple Dockerfile for this example:

FROM alpine:latest
MAINTAINER You &lt;you@example.com&gt;
COPY hello /usr/bin
EXPOSE 8080
CMD [&quot;/usr/bin/hello&quot;]

However this brings us to a problem: We can not use go:generate to produce the docker image, as of the time when we need to call go:generate, the new binary is not build yet. This would make us do ugly things like

go generate &amp;&amp; go build &amp;&amp; go generate

leading to the docker image build twice and whatnot. So, we need a different solution

Solution A: A shell script

We could of course come up with something like:

#!/bin/bash
# Checks for existence omitted for brevity
GO=$(which go)
DOCKER=$(which docker)
$GO generate
$GO test
$GO build
$DOCKER -t you/hello .

However, this comes with a problem: you will always do the whole sequence using the shell script. Even when you just want to run the tests, you would end up building the docker image. Over time, this adds up. In such situations I tend to use

Solution B: a Makefile

A Makefile is a configuration file for GNU make

	CC = $(shell which go 2&gt;/dev/null)
	DOCKER = $(shell which docker 2&gt;/dev/null)

	ifeq ($(CC),)
	$(error &quot;go is not in your system PATH&quot;)
	else
	$(info &quot;go found&quot;)
	endif

	ifeq ($(DOCKER),)
	$(error &quot;docker not in your system path&quot;)
	else
	$(info &quot;docker found&quot;)
	endif

	.PHONY: clean generate tests docker all

	all: clean generate tests hello docker

	clean:
		$(RM) hello rice-box.go cover.out
	generate:
		$(CC) generate
	tests: generate
		$(CC) test -coverprofile=cover.out
	hello: tests
		$(CC) build
	docker: hello
		$(DOCKER) build -t sosample/hello .

A full explanation is beyond the scope of this answer, but what you can basically do here is that you can call make and the all target will be built: files from the old build are removed (clean), a new rice-box.go is generated (generate) and so on. But in case you only want to run the tests for example, calling make test would only execute the targets clean, generate and tests.

答案2

得分: 0

你可以查看我的方法来组织你的项目,链接在这里:https://github.com/alehano/gobootstrap
这是一个Web框架。

英文:

You can take a look to my approach to structure your project https://github.com/alehano/gobootstrap
It's a web framework.

huangapple
  • 本文由 发表于 2017年8月1日 05:41:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/45425749.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定