Мотивация для бэкэндеров изучать Golang
Мотивация для бэкэндеров изучать Golang

Привет, Хабр! У многих разработчиков на .NET вызывает интерес относительно свежий язык программирования Go (Golang). Однако при поиске информации и учебных материалов он может отпугивать. Нам предлагается забыть все удобное и красивое, чему нас научила .NET, и принять что-то новое, но кажущееся непривычным и не всегда приятным.

И к проблеме непривычности добавляется отсутствие качественного материала на русском языке. Большинство книг поверхностно рассматривают стандартные для всех языков ключевые слова, не углубляясь в важные аспекты их внутреннего устройства и работы.

В своей статье я хочу поэтапно описать все необходимые шаги для создания простого микросервиса и представить его в виде шаблона. Так как я сам не являюсь опытным разработчиком на Go, а только изучаю этот язык, мой шаблон предназначен для того, чтобы показать, как примерно выглядит микросервис.

Содержание

В данной статье я не буду углубляться в тонкости языка и объяснять все детали. Статья предназначена для опытных разработчиков других языков, которые изучают Go и которым не хватает примеров и шаблонов для построения API в учебных целях.

Мы создадим проект с нуля, добавим вычитывание конфигурации, настройку инфраструктуры, настройку DI и настройку HTTP-сервера, и всё это запустим.

Создаем проект

Для разработки я буду использовать IDE GoLand.

Создаем новый проект и нас встречает пустой каталог с файлом go.mod, который содержит версию языка и нашем случае - это 1.22

Для организации архитектуры каталогов проекта буду использовать на работки из этой статьи - структурирование проекта на golang

Создаем каталог с стартовой точкой нашего приложения, аналог из .NET файл Program.cs.

cmd/app/main.go
package main

import "fmt"

func main() {
	fmt.Println("Hello World")
}

В языке программирования Go, как и во многих других языках, точкой входа в приложение является функция main. Для создания IDE автоматически профиля сборки эта функция должна быть размещена в пакете main. Наименование пакетов в Go является аналогом неймспейсов в .NET.

Настройка конфигурации

Конфигурация проекта осуществляется с помощью environments. Я не буду задавать их в системе или конфигурационном файле проекта, а для удобства использую библиотеку, которая загружает конфигурацию из файла в переменные окружения (Environment Variables).

База данных, которая будет использоваться в проекте postgresql. Дальнейшая ее настройка и поднятие через docker-compose.yml будут в следующих пунктах, а пока занимаемся настройкой вычитки из конфигурационного файла.

Для начала создадим файл для локального запуска конфигурации.

configs/local.env
DB_CONFIG_USER=golang-template-service
DB_CONFIG_PASSWORD=golang-template-service
DB_CONFIG_DBNAME=golang-template-service
DB_CONFIG_HOST=127.0.0.1
DB_CONFIG_PORT=5432

Теперь создадим структуру, которая будет отражать наш конфигурационный файл. Для этого нам потребуется установить необходимый пакет. В терминале, в корневом каталоге нашего проекта, выполните следующую команду.

go get "github.com/joho/godotenv"

Саму структуру, которую мы будем использовать для конфигурации, поместим в каталог internal. В языке Go все, что помещено в каталог internal, может использоваться только в рамках данного проекта и не может быть использовано как подключаемый пакет. Это ограничение действует автоматически благодаря названию каталога.

Создаем структуру для конфигурации базы данных.

internal/config/database/config.go
package database

// Config - Конфигурация для подключения к БД
type Config struct {
	User     string
	Password string
	Host     string
	Port     int
	DbName   string
}

Также создаем helper, для подгрузки конфигурации из файла с помощью установленной библиотеки и вычитки различных типов из environment.

internal/config/config_helper.go
package config

import (
	"github.com/joho/godotenv"
	"log"
	"os"
	"strconv"
)

// LoadEnvironment - загрузить из файла конфигурацию в environments
func LoadEnvironment() {
	err := godotenv.Load("configs/local.env")
	if err != nil {
		log.Fatal("Error loading .env file")
	}
}

// getEnv - считать environment в формете string
func getEnv(key string, defaultVal string) string {
	if value, exists := os.LookupEnv(key); exists {
		return value
	}

	return defaultVal
}

// getEnvAsInt - считать environment в формете int
func getEnvAsInt(name string, defaultVal int) int {
	valueStr := getEnv(name, "")
	if value, err := strconv.Atoi(valueStr); err == nil {
		return value
	}

	return defaultVal
}

Теперь создаем общий конфиг приложения, который будет с помощью DI внедряться в наши сервисы. Настройка DI будет далее.

internal/config/config.go
package config

import "src/internal/config/database"

// Config - Главный конфиг приложения
type Config struct {
	Database *database.Config
}

func NewConfig() *Config {
	return &Config{
		Database: &database.Config{
			User:     getEnv("DB_CONFIG_USER", "root"),
			Password: getEnv("DB_CONFIG_PASSWORD", "root"),
			Host:     getEnv("DB_CONFIG_HOST", "localhost"),
			Port:     getEnvAsInt("DB_CONFIG_PORT", 3306),
			DbName:   getEnv("DB_CONFIG_DBNAME", ""),
		},
	}
}

Теперь возвращаемся в функцию main и проверяем вычитывание нашей конфигурации

cmd/app/main.go
package main

import (
	"fmt"
	"src/internal/config"
)

func main() {
	// Вызываем подгрузку конфигурации
	config.LoadEnvironment()

	// Создаем конфиг
	appConfig := config.NewConfig()

	fmt.Println(fmt.Sprintf("User: %s", appConfig.Database.User))
	fmt.Println(fmt.Sprintf("Host: %s", appConfig.Database.Host))
	fmt.Println(fmt.Sprintf("Password: %s", appConfig.Database.Password))
	fmt.Println(fmt.Sprintf("DbName: %s", appConfig.Database.DbName))
	fmt.Println(fmt.Sprintf("Port: %d", appConfig.Database.Port))
}

Все работает и идем далее.

Настройка HTTP-сервера

Теперь нам необходимо подготовить всё для запуска HTTP-сервера и настройки маршрутизации. Выполните следующую команду в корневом каталоге проекта для установки необходимых пакетов.

go get "github.com/codegangsta/negroni"
go get "github.com/gorilla/mux"

Далее создадим файл заготовку для маршрутизации

api/router/router.go
package router

import (
	"github.com/gorilla/mux"
)

// Router - структура описывающие маршрутизацию контроллеров в нашем приложении
type Router struct {
}

// NewRouter Метод для инициализации структуры в DI
func NewRouter() *Router {
	return &Router{}
}

// InitRoutes - инициализация маршрутизации API
func (routes *Router) InitRoutes() *mux.Router {
	router := mux.NewRouter()
	return router
}

После этого создадим файл с настройками сервера, его инициализацией и запуском.

server/server.go
package server

import (
	"github.com/codegangsta/negroni"
	"src/api/router"
	"src/internal/config"

	"net/http"
)

// Server - структура сервера
type Server struct {
	AppConfig *config.Config
	Router    *router.Router
}

// NewServer Метод для инициализации структуры в DI
func NewServer(appConfig *config.Config, router *router.Router) *Server {
	return &Server{
		AppConfig: appConfig,
		Router:    router,
	}
}

// Run - метод для запуска нашего http-сервера
func (server *Server) Run() {
	ngRouter := server.Router.InitRoutes()
	ngClassic := negroni.Classic()
	ngClassic.UseHandler(ngRouter)
	err := http.ListenAndServe(":5000", ngClassic)
	if err != nil {
		return
	}
}

Настройка DI

Сейчас не будем запускать сервер и проверять его работу. Сначала настроим DI (внедрение зависимостей) для нашего сервиса. Выполните следующую команду для установки необходимого пакета.

go get "go.uber.org/dig"

Далее создадим app.go файл в котором будет происходит конфигурация нашего DI

internal/app/app.go
package app

import (
	"go.uber.org/dig"
	"src/api/router"
	"src/internal/config"
	"src/server"
)

func BuildContainer() *dig.Container {
	container := dig.New()

	_ = container.Provide(config.NewConfig)
	_ = container.Provide(server.NewServer)
	_ = container.Provide(router.NewRouter)

	return container
}

Теперь, когда контейнер настроен, возвращаемся в main.go, собираем контейнер и запускаем наш HTTP-сервер.

cmd/app/main.go
package main

import (
	"src/internal/app"
	"src/internal/config"
	"src/server"
)

func main() {
	// Вызываем подгрузку конфигурации
	config.LoadEnvironment()

	// Билдим наш контейре с зависимостями
	container := app.BuildContainer()

	// Запускаем наш HTTP-сервер
	err := container.Invoke(func(server *server.Server) {
		server.Run()
	})

	if err != nil {
		panic(err)
	}
}

Проверяем, что наше приложение не завершается и продолжает работать HTTP-сервер и идем далее.

Настраиваем заготовку репозитория, сервиса, контроллера.

Далее создадим всё необходимое: от репозитория до контроллера, зарегистрируем маршрутизацию и добавим всё это в DI.

Выполняем команду ниже и скачиваем пакет для работы с uuid.

go get "github.com/google/uuid"

Создаем сущность в нашем случае это будет книга

internal/entities/book/book_entity.go
package book

import "github.com/google/uuid"

// Entity - модель в БД для нашей книги
type Entity struct {
	Uuid uuid.UUID
	Name string
}

Далее создаем репозиторий

internal/repositories/book/book_repository.go
package book

import (
	"fmt"
	"src/internal/entities/book"
)

// Repository - Структура репозитория
type Repository struct {
	database []book.Entity
}

// NewRepository - Метод для регистрации в DI
func NewRepository() *Repository {
	return &Repository{
		database: make([]book.Entity, 0),
	}
}

// Create - добавить книгу
func (repository *Repository) Create(entity book.Entity) {
	repository.database = append(repository.database, entity)
	fmt.Println(repository.database)
}

Теперь создаем модель для нашего сервиса

internal/models/book/book_create_model.go
package book

// CreateModel - Модель создания книги
type CreateModel struct {
	Name string `json:"name" form:"name"`
}

Создадим наш сервис

internal/services/book/book_service.go
package book

import (
	"github.com/google/uuid"
	bookEntities "src/internal/entities/book"
	bookService "src/internal/models/book"
	"src/internal/repositories/book"
)

// Service - для работы с книгами
type Service struct {
	repository *book.Repository
}

// NewService - метод для регистрации в DI
func NewService(repository *book.Repository) *Service {
	return &Service{repository: repository}
}

// Create - метод создания книги
func (service *Service) Create(model *bookService.CreateModel) {
	// Создаем сущность
	bookEntity := bookEntities.Entity{
		Uuid: uuid.New(),
		Name: model.Name,
	}

	service.repository.Create(bookEntity)
}

Базовые вещи подготовлены. Теперь нам нужно создать контроллер, прописать для него маршрутизацию и зарегистрировать всё это в DI.

Создаем наш контроллер

api/controllers/book/book_controller.go
package book

import (
	"encoding/json"
	"net/http"
	bookModels "src/internal/models/book"
	"src/internal/services/book"
)

// Controller - контроллер для работы с книгами
type Controller struct {
	service *book.Service
}

// NewController - мето для регистрации контроллера DI
func NewController(service *book.Service) *Controller {
	return &Controller{
		service: service,
	}
}

func (controller *Controller) CreateBook(w http.ResponseWriter, r *http.Request) {
	request := new(bookModels.CreateModel)
	decoder := json.NewDecoder(r.Body)
	_ = decoder.Decode(&request)

	controller.service.Create(request)

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
}

Теперь создадим маршрутизацию для нашего контроллера

api/controllers/book/book_controller_route.go
package book

import (
	"github.com/gorilla/mux"
)

// ControllerRoute настройки маршрутизации для нашего контроллера
type ControllerRoute struct {
	Controller *Controller
}

// NewControllerRoute Метод для регистрации в DI
func NewControllerRoute(controller *Controller) *ControllerRoute {
	return &ControllerRoute{Controller: controller}
}

// Route добавить в роутер маршрут
func (route *ControllerRoute) Route(router *mux.Router) *mux.Router {
	router.HandleFunc("/api/books", route.Controller.CreateBook).Methods("POST")
	return router
}

Теперь нам необходимо вернуться в наш router.go и добавить наш контроллер и маршрутизацию.

api/router/router.go
package router

import (
	"github.com/gorilla/mux"
	"src/api/controllers/book"
)

// Router - структура описывающие маршрутизацию контроллеров в нашем приложении
type Router struct {
	BookRoutes *book.ControllerRoute
}

// NewRouter Метод для инициализации структуры в DI
func NewRouter(bookRoutes *book.ControllerRoute) *Router {
	return &Router{
		BookRoutes: bookRoutes,
	}
}

// InitRoutes - инициализация маршрутизации API
func (routes *Router) InitRoutes() *mux.Router {
	router := mux.NewRouter()
	router = routes.BookRoutes.Route(router)
	return router
}

Далее необходимо вернуться в app.go и добавить регистрацию в DI всего, что мы добавили

api/internal/app/app.go
package app

import (
	"go.uber.org/dig"
	"src/api/controllers/book"
	"src/api/router"
	"src/internal/config"
	bookRepository "src/internal/repositories/book"
	bookService "src/internal/services/book"
	"src/server"
)

func BuildContainer() *dig.Container {
	container := dig.New()

	_ = container.Provide(config.NewConfig)
	_ = container.Provide(server.NewServer)
	_ = container.Provide(router.NewRouter)
	
	buildBook(container)

	return container
}

func buildBook(container *dig.Container) {
	_ = container.Provide(book.NewController)
	_ = container.Provide(book.NewControllerRoute)
	_ = container.Provide(bookService.NewService)
	_ = container.Provide(bookRepository.NewRepository)
}

Теперь все готово для нашего первоначального запуска и первого запроса

Полетели

Запускаем и с помощью postman отправляем наш первый запрос и видим, что все заработало.

Заключение

В этой части мы создали всю необходимую первоначальную инфраструктуру для нашего сервиса: настроили конфигурацию, внедрение зависимостей (DI) и запуск HTTP-сервера. В следующей части я планирую подключить базу данных, настроить middleware для логирования, добавить Swagger и, возможно, включить ещё несколько полезных элементов.

Также если есть какие-то замечания или пожелания пишите в комментариях, или создавайте PR в проект.

Ссылка на сам проект

Спасибо за внимание! Надеюсь кому-то данный материал поможет первоначально разобраться.

Комментарии (25)


  1. gudvinr
    02.06.2024 16:28
    +6

    Однако при поиске информации и учебных материалов он может отпугивать.

    Ваш материал больше отпугивает, чем уже существующая информация.

    Вместо того, чтобы переосмыслить своё мышление и писать на Go, вы свои .NET привычки просто затащили в другую среду и получилось это


    1. ItwithMisha Автор
      02.06.2024 16:28

      Переосмыслить свое мышление и писать на Go для меня звучит слишком абстрактно, вы можете указать какие моменты, вы считаете неправильными и я бы с удовольствием для себя, что-то подчеркнул.

      А просто в очередной раз слышать про переключение мышление, делу не поможет. Для меня язык программирования всего-лишь инструмент и я стараюсь привести решение, к общему формату, которое будет понятно большому числу разработчиков.

      Я параллельно работая на .NET стараюсь изучать Go и достаточно сложно просто взять, выкинуть весь предыдущий опыт и начать писать "правильно". По-этому сначала делаю шаблон по классике, а в дальнейшем буду разбирать и что-то улучшать и мне вот в свое время не хватало таких статей, чтобы в целом понять.


      1. lnkiseleva
        02.06.2024 16:28
        +2

        Так же как и вы перешла, и тоже сначала пробовала ООП практики по привычке использовать. В общем наверное можно и так, но можно попробовать по другому. Что помогло мне это прежде всего чтение исходников стандартной библиотеки и на английском статьи и видео от William Kennedy. Ещё можно почитать например "Функциональное мышление" или что-то похожее, чтобы обновить подход (или как выше писали переосмыслить мышление).


      1. gudvinr
        02.06.2024 16:28
        +1

        Для меня язык программирования всего-лишь инструмент

        Пила и молоток - тоже инструменты, но вы вместо того чтобы забивать гвоздь молотком пытаетесь им пилить, потому что "стараетесь привести решение к общему формату"

        Любым инструментом надо тоже уметь пользоваться. И это касается не только Go, но и другого языка.

        которое будет понятно большому числу разработчиков

        Оно не будет понятно никому кроме вас и, возможно, других людей, которые один раз выучили программирование по гайдам для .NET и решили, что программирование - это только так.

        В итоге, вы не пишете микросервисы на Go, вы пишете микросервисы, пытаясь натянуть синтаксис Go на парадигмы .NET.

        А просто в очередной раз слышать про переключение мышление, делу не поможет.

        Какому делу? Научить вас использовать инструмент? Так это ваше дело, а не кого-либо ещё. Только вот если вы будете писать так в Go проекте, который разрабатывают люди, которые действительно используют Go, то как минимум, на вас будут косо смотреть.

        вы можете указать какие моменты, вы считаете неправильными и я бы с удовольствием для себя, что-то подчеркнул

        Почитайте Effective Go, к примеру.

        Уже с первых строк становится понятно, что вы используете пакеты как аналог классов, чтобы покрыть отсутствие инкапсуляции на уровне (отсутствующих) классов. Проблема в том, что Go - это не объектно-ориентированный язык.

        Плюс вы сразу гвоздями прибили проект к определённой структуре, хотя в этом абсолютно никакой необходимости нет. Можно без проблем выделять сущности в отдельные структуры внутри одного пакета, пока у вас не появляется необходимости в дополнительном ограничении. Вы же не библиотеку пишете, а сервис, где единственный потребитель программных интерфейсов между сущностями - это сам сервис.

        Пакеты с одним файлом в них - это антипаттерн, который больше мешает, чем помогает.

        Кучу пакетов втянули просто потому что. Никак не поясняя необходимость, опять же скорее всего потому что вы привыкли к тому, что это так делается в .NET и несмотря на то, что функционал некоторых пакетов есть в стандартной библиотеке.


        1. ItwithMisha Автор
          02.06.2024 16:28

          Пила и молоток - тоже инструменты, но вы вместо того чтобы забивать гвоздь молотком пытаетесь им пилить, потому что "стараетесь привести решение к общему формату"

          Любым инструментом надо тоже уметь пользоваться. И это касается не только Go, но и другого языка.

          Не совсем согласен с данным примером. Если есть какие-то паттерны и подходы ничто не мешает им ложится на множество языков. Go не такой уже и особенной, что на нем все нужно делать, как вы указали.

          Оно не будет понятно никому кроме вас и, возможно, других людей, которые один раз выучили программирование по гайдам для .NET и решили, что программирование - это только так.

          В итоге, вы не пишете микросервисы на Go, вы пишете микросервисы, пытаясь натянуть синтаксис Go на парадигмы .NET.

          Причем тут выучили по гайдам. Есть определенная структура построения приложения для того, чтобы когда приходили новые разработчики, видели примерно тоже самое, что и на своей предыдущей работе. Все равно должна быть какая-то структура

          Почитайте Effective Go, к примеру.

          Благодарю изучу, но в целом тут общее описания синтаксиса языка без структуры.

           Проблема в том, что Go - это не объектно-ориентированный язык.

          Тут сложно сказать, все-таки язык содержит в себе объектно-ориентированные признаки, которые можно и когда требуется нужно использовать. Мы же не на чистом си пишем, где только одни функции и все

          Плюс вы сразу гвоздями прибили проект к определённой структуре, хотя в этом абсолютно никакой необходимости нет. Можно без проблем выделять сущности в отдельные структуры внутри одного пакета, пока у вас не появляется необходимости в дополнительном ограничении.

          С этим я согласен, что нужно пересмотреть раскидывание по пакетам


          1. gudvinr
            02.06.2024 16:28

            Если есть какие-то паттерны и подходы ничто не мешает им ложится на множество языков.

            Если у вас есть паттерны, которые были спроектированы дедами для ООП, это очень даже мешает их использовать для языков, в которых нет ООП.

            Go не такой уже и особенной, что на нем все нужно делать, как вы указали.

            В том и дело, что я не говорю, как надо делать в каких-то конкретных ситуациях, я говорю, что изучая новую технологию, нужно проявлять гибкость ума, чтобы писать идиоматичный код, который будет понятен и привычен не только вам.

            Любой инструмент имеет свои особенности, с которыми надо уметь работать. Если бы человек, который всю жизнь писал на C++ начал писать на .NET так, как он привык, то вы бы наверняка смотрели на код и думали: ну какой же чудак, ну кто так пишет.

            Есть определенная структура построения приложения

            Если вы имеете в виду структуру пакетов и директорий, то в документации языка нет ни слова о какой-то особой структуре проектов на Go.

            Если речь о том, что "микросервис" какие-то определенные типы должен собой представлять - тут тоже мимо. Каждый пилит то, во что горазд и то, что нужно для конкретного проекта. Не говоря уже о том, что даже готовые фреймворки между собой не совместимы.

            В итоге ваша "определённая структура" - это такая же незнакомая для других разработчиков структура, как и любая другая. Потому что никакого стандарта, которому должны следовать все, не существует.


            1. ItwithMisha Автор
              02.06.2024 16:28

              Тут возможно, что язык молодой и еще появиться какая-то структурированность) В целом я согласен, что не нужно писать .NET, как на С++ если вы переходите на другой стек.

              Все равно в целом нужно видимо больше времени, чтобы подстроиться под новый инструмент. Так что не буду тут спорить, так не являюсь опытным в гоу разработке, просто для себя изучаю и разбираю, почему он так популярен)


              1. gudvinr
                02.06.2024 16:28

                Тут возможно, что язык молодой

                First appeared: November 10, 2009; 14 years ago. Это меньше 10 лет разницы с .NET Framework, в принципе во многих странах с такого возраста уже работать можно.


  1. xemos
    02.06.2024 16:28
    +3

    Так зачем с .net на go переходить?


    1. ItwithMisha Автор
      02.06.2024 16:28

      Вариантов думаю масса и самый главный, который интересует большинство, что больше платят)

      А так как я понял область применения, что из-за того, что он компилируемый и занимает мало место. На нем пишут всякое ПО для устройств, умной колонки, чтобы было возможно быстро заслать обновление.

      Также делать классические API Crud сервисы, где требуется высокая производительность и также всякие потоковые штуки, трансляции видео.

      Ну и в целом интересно что-то новое изучить


      1. asilzhan11
        02.06.2024 16:28
        +1

        Также делать классические API Crud сервисы, где требуется высокая производительность и также всякие потоковые штуки, трансляции видео.

        Это причина перейти с .NET на Go?


        1. ItwithMisha Автор
          02.06.2024 16:28

          Я вообще не топлю за переход с .NET на Go. Причины могут быть разные, мне в целом интересно потыкать что-то новое, вдруг придет откровение)


      1. gudvinr
        02.06.2024 16:28
        +1

        А так как я понял область применения, что из-за того, что он компилируемый и занимает мало место.

        У вас видимо свой интернет. Если бы вы посмотрели размер вашего же бинарника, на основе кода для которого вы статью написали, то заметили бы, что он очень большой.

        Один только runtime для hello world будет весить около 3-4мб. Это очень много и на Go пишут точно не из-за малого размера бинарников. Да, есть tinygo, но это не совсем Go и поведение программ может отличаться. Ну и он не очень-то и популярный за пределами DIY.

        Также делать классические API Crud сервисы, где требуется высокая производительность и также всякие потоковые штуки, трансляции видео.

        Если в такие программы пихать рефлексию не задумываясь, как вы делаете, то высокой производительности можно и не ожидать.


        1. ItwithMisha Автор
          02.06.2024 16:28

          Вам лишь бы поспорить)

          Про размер файла я не сравнивал со своим шаблоном микросервиса, а просто отвечал про области применения и где-то читал статью, как после перехода с Java размер бинарника был значительно меньше. Все равно даже если 3-4 мб много, на джаве вероятно будет весить больше.

          А что я пихал из рефлексии? Просто добавил DI контейнер, нужны все-таки объективные замеры на сколько падает производительность, если использовать данный пакет

          И в целом статья не почему стоит перейти на Go, а позволить потыкать что-то посмотреть в разрезе, как было на исходном языке с классическим ООП.


      1. amironov
        02.06.2024 16:28

        А так как я понял область применения, что из-за того, что он компилируемый и занимает мало место

        В .Net сейчас AOT есть, тоже позволяет собрать небольшой бинарик.


  1. snark87
    02.06.2024 16:28
    +5

    Я вот тоже когда-то переходил из .NET в Go.

    Несколько случайных замечаний:

    - Название модуля в go.mod: обычно оно не src, а github.com/blahblah/blahblahblah. Это имеет значение, если кто-то будет импортировать ваш код. И совсем не обязательно помещать весь код в src.

    - Вы начали использовать пакет internal для кода, который не должен переиспользоваться вне проекта, но при этом по какой-то загадочной причине контроллер и роутер не попадают в internal. По-хорошему, практически весь код, кроме main, может (и должен) быть в internal.

    - Я очень понимаю желание использовать DI-контейнеры после многолетнего опыта работы с .NET, но по сути для микросервисов (если они, конечно, микро) большой пользы они не приносят - достаточно просто построить дерево зависимостей вручную.

    - Если уж использовать ООП, то зависимости контроллеров, роутеров итп должны быть от интерфейсов, а не от реализаций. Ну, и тестов нет :-( Зачем городить многослойность  контроллер-сервис-репозиторий, если нет ни бизнес-логики, ни доменной модели, ни тестов? А если и делать многослойную архитектуру (с расчетом, что когда-то микросервис станет гигантским монолитом), то почему вдруг сервис зависит от моделей API?

    - Зачем экспортировать все поля в структурах таких, как Router, ControllerRoute, Server?

    - В методе CreateBook контроллера отсутствует обработка ошибок - ошибки попросту игнорируются, что нехорошо. То же в методе Run сервера, который по-хорошему должен был бы возвращать ошибку.

    - В методе getEnvAsInt: код на Go читается гораздо удобнее, если сверху вниз - happy path, магистральный сценарий, а все if обрабатывают ошибочные ситуации. Если поменять инвертировать if err == nil, чтобы стало if err != nil, то код станет каноничнее.

    Удачи!


    1. ItwithMisha Автор
      02.06.2024 16:28

      Благодарю за подробный ответ)

      Название модуля в go.mod: обычно оно не src, а github.com/blahblah/blahblahblah. Это имеет значение, если кто-то будет импортировать ваш код. И совсем не обязательно помещать весь код в src.

      Это важный момент, спасибо на будущее запишу себе!

      Я очень понимаю желание использовать DI-контейнеры после многолетнего опыта работы с .NET, но по сути для микросервисов (если они, конечно, микро) большой пользы они не приносят - достаточно просто построить дерево зависимостей вручную.

      Да после .NET очень тянет к DI контейнерам, но согласен, что тут можно обойтись без этого, чтобы лишний раз все не прописывать

      Вы начали использовать пакет internal для кода, который не должен переиспользоваться вне проекта, но при этом по какой-то загадочной причине контроллер и роутер не попадают в internal. По-хорошему, практически весь код, кроме main, может (и должен) быть в internal.

      Понял, с этим тоже согласен. В принципе наверное у микросервиса можно вынести в пакет, только какие-нибудь контракты, но нужно ли это в гоу)

      По валидации параметров я еще не добавлял, хотел далее. Остальные ваще, спасибо, что описали использую для того, чтобы к следующей статье подредактировать проект.


      1. GroVlAn
        02.06.2024 16:28
        +2

        Ещё про хотелось бы дополнить про internal, не обязательно весь код в нём хранить, в нем нужно хранить код самого проекта, что не будет импортироваться в другой проект, если есть код, который вы собираетесь использовать в другой проект, хорошей практикой будет его хранить в pkg, так вы сразу обозначите, что этот код может использовать кто то ещё


  1. qoonmax
    02.06.2024 16:28
    +2

    Платят чуть больше в среднем процентов на 10-20%. Мне кажется, если цель поднять зарплату просто чуть дольше поискать вакансию на .NET.

    На больших цифрах так вообще не очень интересно. Вот вам сильно критично получать 400к или 330к условно?

    В добавок учтите, что статистика дело такое, даже если в Go платят чуть больше, вы можете вполне залутать низ рынка, потому что у вас опыта на Go мало.

    Go отличный, но мотивация плохая. Как известно, лучший язык который нужно изучать для роста зарплаты это английский.


    1. wlbm_onizuka
      02.06.2024 16:28
      +1

      между 400к и 330к разница колоссальная, т.к 250к (подставьте свою цифру) уходит на жизнь. итого разница в 2 раза на свои хотелки


      1. qoonmax
        02.06.2024 16:28
        +1

        Это уже хитрости сравнения. Конечно 330к и 400к это я большой разрыв взял, но даже если отталкиваться от него как от плохого сценария, то скажу так, задача "Выучить Go" это на несколько лет, на изучение нишевых задач и решений на Go, на получение интересного работодателю опыта.

        Ведь на Go не платят за знание синтаксиса и умение написать микросервис с 2-мя крудами. Вероятно вы просядете по зарплате, пока не получите вкусный релевантный опыт. Следовательно часть дохода вы упускаете ежемесячно.

        С маленьким опытом в других языках вы в Go никому не нужны, это 1000%. А с большим опытом в другом языке вы вероятно заработаете больше, нежели от смены стека.


        1. ItwithMisha Автор
          02.06.2024 16:28

          Я думаю переход с другого языка и изучения с самого начала немного разные вещи. Ведь весь опыт работы на бекенде остается вместе с тобой и остальная инфраструктура не меняется. Типо контейнеры, куберы, кафки, реббиты. Да определенно нужно время, но я думаю вполне себе за пол года можно.


          1. qoonmax
            02.06.2024 16:28
            +1

            Разумеется это так. Но в глазах работодателя вы .Net Developer переходящий в Go, а не Golang developer. А чтобы претендовать на топ зарплат Go рынка, нужно как минимум год хорошего опыта на Go стеке.


            1. ItwithMisha Автор
              02.06.2024 16:28

              Это да, мне кажется сначала нужно искать в компании Go отделы и просится туда, а там пойдет. Выходит так просто на рынок согласен сложно)


  1. vsozinov
    02.06.2024 16:28
    +1

    У вас описка:
    got get "github.com/joho/godotenv"