• Как пробежаться по массиву и сделать Insert в бд?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Если брать за основу ваш код - примерно так можно сделать
    package main
    
    import "fmt"
    
    type User struct {
      UserName string
      Category string
      Age      int
    }
    
    func main() {
    
      users := []User{
        User{UserName: "Bryan", Category: "Human", Age: 33},
        User{UserName: "Jane", Category: "Rocker", Age: 25},
        User{UserName: "Nancy", Category: "Mother", Age: 40},
        User{UserName: "Chris", Category: "Dude", Age: 19},
        User{UserName: "Martha", Category: "Cook", Age: 52},
      }
    
      // подключаетесь к базе
      db, _ := sql.Open(...)
      defer db.Close()
    
      for _, user := range users {
          fmt.Println(user)
          // делаете запрос в базу
          db.Exec("INSERT INTO users (name, category, age) VALUES ($1, $2, $3)", user.UserName, user.Category, user.Age)
      }
    }

    Если данных для вставки в базу будет много - такой вариант будет тормозить, в этом случае лучше делать вставку в транзакции
    tx, _ := db.Begin()  // начинаете транзакцию
    for _, user := range users {
        fmt.Println(user)
        // делаете запрос в базу (тут уже не db, а tx.Exec)
        tx.Exec("INSERT INTO users (name, category, age) VALUES ($1, $2, $3)", user.UserName, user.Category, user.Age)
    }
    tx.Commit() // завершаете транзакцию, данные сохраняются в базе
    Ответ написан
  • Как правильно работать с json в Go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вам нужно заменить Object []byte на Object json.RawMessage
    Ответ написан
    2 комментария
  • Работа с goroutines?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Есть несколько вариантов решения задачи
    передавать не указатель, а значение

    package main
    
    import (
      "fmt"
      "time"
    )
    
    var c = make(chan int, 3)
    var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    func main() {
      fmt.Println("Hello, playground")
      go save()
      go read()
      time.Sleep(3 * time.Second)
    }
    
    func save() {
      for _, val := range data {
        c <- val
      }
    }
    
    func read() {
      for {
        val := <-c
        fmt.Println("read:", val)
      }
    }

    скопировать значение и передать указатель на это значение

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var c = make(chan *int)
    var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    func main() {
    	fmt.Println("Hello, playground")
    	go save()
    	go read()
    	time.Sleep(3 * time.Second)
    }
    
    func save() {
    	for _, val := range data {
    	        v := val
    		c <- &v
    	}
    }
    
    func read() {
    	for {
    		val := <-c
    		fmt.Println("read:", *val)
    	}
    }

    передать правильный указатель, на элемент данных

    package main
    
    import (
    	"fmt"
    	"time"
    
    
    var c = make(chan *int, 5)
    var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    func main() {
    	fmt.Println("Hello, playground")
    	go save()
    	go read()
    	time.Sleep(3 * time.Second)
    }
    
    func save() {
    	for i := range data {
    		c <- &data[i]
    	}
    }
    
    func read() {
    	for {
    		val := <-c
    		fmt.Println("read:", *val)
    	}
    }



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

    Вот пример, который покажет, что адрес всегда один и тот же
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var c = make(chan *int, 5)
    var data = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    func main() {
    	fmt.Println("Hello, playground")
    	go save()
    	go read()
    	time.Sleep(3 * time.Second)
    }
    
    func save() {
    	for _, val := range data {
    		c <- &val
    		fmt.Printf("write: %v\n", &val)
    	}
    }
    
    func read() {
    	for {
    		val := <-c
    		fmt.Println("read:", *val)
    	}
    }

    Вывод будет таким (адрес один и тот же)
    Hello, playground
    write: 0xc000094000
    write: 0xc000094000
    write: 0xc000094000
    write: 0xc000094000
    write: 0xc000094000
    write: 0xc000094000
    ...


    А в такой реализации (2й пример)
    func save() {
    	for _, val := range data {
    		v := val
    		c <- &v
    		fmt.Printf("write: %v\n", &v)
    	}
    }

    тут вывод будет таким (каждый раз новый участок памяти)
    Hello, playground
    write: 0xc000094000
    write: 0xc000094010
    write: 0xc000094018
    write: 0xc000094020
    write: 0xc000094028
    write: 0xc000094030

    В третьем варианте тоже будут разные указатели.
    Ответ написан
    5 комментариев
  • Как организовать код в интерфейсы golang?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Интерфейс нужно будет указывать как параметр функций, которые будут уметь работать с вашим интрейфейсом.
    Например.
    func Process(device test123.Command) {
        device.Open()
        device.Close()
    }
    
    dev1 := a123.A123struct{параметры...}
    Process(dev1)
    
    dev2 := b123.B123struct{параметры...}
    Process(dev2)
    
    // или с перечислением
    devices := []test123.Command{dev1, dev2}
    for _, device := range devices {
        Process(device)
    }


    Приведу пример более похожий на реальный.
    Допустим нам нужно сделать логгер, который умеет писать в файл (если указан в настройках), а если не указан - тогда в консоль.
    Пример намеренно упрощён, чтобы было проще уловить суть.
    package main
    
    import (
        "fmt"
        "os"
    )
    
    // Logger интерфейс логгера.
    type Logger interface {
        Error(msg string)
    }
    
    // StdoutLogger реализация интерфейса Logger для вывода сообщений в консоль.
    type StdoutLogger struct{}
    
    // NewStdoutLogger конструктор (возвращаем структуру, не интерфейс)
    func NewStdoutLogger() *StdoutLogger {
        return &StdoutLogger{}
    }
    
    // Error добавляет в лог сообщение с уровнем error
    func (l *StdoutLogger) Error(msg string) {
        fmt.Printf("ERROR: %s\n", msg)
    }
    
    // FileLogger реализация интерфейса Logger для вывода сообщений в файл.
    type FileLogger struct {
        FileName string
        Fh       *os.File
    }
    
    // NewFileLogger конструктор.
    func NewFileLogger(fileName string) *FileLogger {
        logger := &FileLogger{
            FileName: fileName,
        }
        fh, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0664)
        if err != nil {
            panic(fmt.Sprintf("FileLogger: can't open log file: %s: %s\n", fileName, err))
        }
        logger.Fh = fh
    
        return logger
    }
    
    // Error добавляет в лог сообщение с уровнем error.
    func (l *FileLogger) Error(msg string) {
        l.Fh.WriteString(fmt.Sprintf("ERROR: %s\n", msg))
    }
    
    // ProcessData какая то функция, которая использует логгер и которую не должна беспокоить реализация логгера.
    // Тут тип параметра должен быть интерфейс.
    func ProcessData(logger Logger) {
        logger.Error("Data process some error happened")
    }
    
    func main() {
        var logger Logger
    
        // если лог файл не указан - используем StdoutLogger, если указан - используем FileLogger
        logFile := os.Getenv("LOG_FILE")
        if logFile != `` {
            logger = NewFileLogger(logFile)
        } else {
            logger = NewStdoutLogger()
        }
    
        ProcessData(logger)
    }

    После запуска go run main.go вы увидите сообщение в консоли:
    ERROR: Data process some error happened
    а если запустите вот так LOG_FILE=test.log go run main.go
    или
    export LOG_FILE=test.log
    go run main.go

    То будет создан файл test.log и туда будет добавлено тоже сообщение, что вы видели в консоли.

    Если понятнее не стало - пишите, будут рад помочь.
    Ответ написан
    2 комментария
  • GoLang, подключение к базе - unexpected EOF в чем причина?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Как правило подобная ситуация говорит о проблемах с подключением к базе, драйверах(библиотеках).
    Первое, что имеет смысл сделать - это настроить соединения:
    db.SetConnMaxIdleTime(0)
    db.SetConnMaxLifetime(0)
    db.SetMaxIdleConns(10)
    db.SetMaxOpenConns(10)


    Если не помогло - нужно убедиться, что база действительно слушает указанный хост и порт в настройках.
    Для PostgreSQL
    psql -h 127.0.0.1 -p 5432 -U user_name  database_name

    Для MySQL
    mysql -h 127.0.0.1 -P 3307 -u user_name -p database_name

    Очень важно чтобы указано было именно 127.0.0.1 (для MySQL), иначе он всё равно будет подключаться по сокету несмотря на то, что указан localhost.

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

    Еще есть смысл смотреть логи базы, там может быть более подробно описана причина обрыва соединения.
    Ответ написан
    Комментировать
  • Почему у меня после Update, Select возвращает старые данные?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Проблема в этом участке, Query используется для извлечения данных, для изменения надо использовать Exec
    update_answer_id_start, err33 := db.Query(fmt.Sprintf("UPDATE `ucp` SET `closed` = '1' WHERE `id` = '%d'", res_trim_int))
    defer update_answer_id_start.Close()


    Например вот так
    update_answer_id_start, err33 := db.Exec("UPDATE `ucp` SET `closed` = '1' WHERE `id` = ?", res_trim_int)
    Ответ написан
    23 комментария
  • Проблема с склеиванием строк, где ошибка в коде?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Нужно внести правки в этот блок кода
    for res_select_list.Next(){
        err18 := res_select_list.Scan(&ucp.Id, &ucp.Text)
        if err18 != nil {
            panic(err18)
        }
        str.WriteString("LIST QUESTION:\r\nID- " + strconv.Itoa(ucp.Id) + " TEXT- " + ucp.Text)
    }


    Сделать вот так
    str.WriteString("LIST QUESTION:\r\n")
    for res_select_list.Next(){
        err18 := res_select_list.Scan(&ucp.Id, &ucp.Text)
        if err18 != nil {
            panic(err18)
        }
        str.WriteString("ID- " + strconv.Itoa(ucp.Id) + " TEXT- " + ucp.Text + "\r\n")
    }
    Ответ написан
    4 комментария
  • Применяется ли во встроенной функции copy отложенное копирование?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Отложенного копирования при копировании слайса нет.
    Вот исходник функции, которая копирует слайсы.
    https://golang.org/src/runtime/slice.go#L34
    В комментарии к функции написано:
    // makeslicecopy allocates a slice of "tolen" elements of type "et",
    // then copies "fromlen" elements of type "et" into that new allocation from "from".


    Исходный код функции на случай, если исходник не доступен

    // makeslicecopy allocates a slice of "tolen" elements of type "et",
    // then copies "fromlen" elements of type "et" into that new allocation from "from".
    func makeslicecopy(et *_type, tolen int, fromlen int, from unsafe.Pointer) unsafe.Pointer {
    	var tomem, copymem uintptr
    	if uintptr(tolen) > uintptr(fromlen) {
    		var overflow bool
    		tomem, overflow = math.MulUintptr(et.size, uintptr(tolen))
    		if overflow || tomem > maxAlloc || tolen < 0 {
    			panicmakeslicelen()
    		}
    		copymem = et.size * uintptr(fromlen)
    	} else {
    		// fromlen is a known good length providing and equal or greater than tolen,
    		// thereby making tolen a good slice length too as from and to slices have the
    		// same element width.
    		tomem = et.size * uintptr(tolen)
    		copymem = tomem
    	}
    
    	var to unsafe.Pointer
    	if et.ptrdata == 0 {
    		to = mallocgc(tomem, nil, false)
    		if copymem < tomem {
    			memclrNoHeapPointers(add(to, copymem), tomem-copymem)
    		}
    	} else {
    		// Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
    		to = mallocgc(tomem, et, true)
    		if copymem > 0 && writeBarrier.enabled {
    			// Only shade the pointers in old.array since we know the destination slice to
    			// only contains nil pointers because it has been cleared during alloc.
    			bulkBarrierPreWriteSrcOnly(uintptr(to), uintptr(from), copymem)
    		}
    	}
    
    	if raceenabled {
    		callerpc := getcallerpc()
    		pc := funcPC(makeslicecopy)
    		racereadrangepc(from, copymem, callerpc, pc)
    	}
    	if msanenabled {
    		msanread(from, copymem)
    	}
    
    	memmove(to, from, copymem)
    
    	return to
    }

    Ответ написан
    Комментировать
  • Как правильно декодировать JSON в UnmarshalJSON?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Есть не очень красивый вариант, но если нужно быстро решить задачу - можно использовать.
    Если у вас нагрузка небольшая - можно его использовать на постоянной основе.
    Если же нагрузка огромная - тогда надо иначе делать.
    Только везде, где символ `_` надо сделать обработку ошибок.
    package main
    
    import (
        "encoding/json"
        "fmt"
        "log"
    )
    
    type TemplateCategory struct {
        ID   int    `json:"id"`
        Name string `json:"name"`
    }
    
    type Template struct {
        Name         string            `json:"name"`
        CategoryInfo *TemplateCategory `json:"category_info"`
    }
    
    func (t *Template) UnmarshalJSON(b []byte) error {
        var result map[string]interface{}
        if err := json.Unmarshal(b, &result); err != nil {
            return err
        }
    
        if t == nil {
            t = &Template{}
        }
        t.Name, _ = result[`name`].(string)
    
        categoryInfo, isMap := result[`category_info`].(map[string]interface{})
        if isMap {
            t.CategoryInfo = &TemplateCategory{}
            t.CategoryInfo.ID, _ = categoryInfo[`id`].(int)
            t.CategoryInfo.Name, _ = categoryInfo[`name`].(string)
        }
    
        return nil
    }
    
    func main() {
        json1 := []byte(`{
            "name": "Мой шаблон",
            "category_info": {
                "id": 109,
                "name": "Тест"
            }
        }`)
    
        json2 := []byte(`{
            "name": "Мой шаблон",
            "category_info": []
        }`)
    
        var data1 Template
        err := json.Unmarshal(json1, &data1)
        if err != nil {
            log.Fatalf(`json1: %s`, err)
        }
    
        var data2 Template
        err = json.Unmarshal(json2, &data2)
        if err != nil {
            log.Fatalf(`json2: %s`, err)
        }
    
        fmt.Printf("data1: %+v\n", data1)
        fmt.Printf("data1.CategoryInfo: %+v\n\n", data1.CategoryInfo)
    
        fmt.Printf("\n\ndata2: %+v\n", data2)
        fmt.Printf("data2.CategoryInfo: %+v\n\n", data2.CategoryInfo)
    }

    Вывод:
    data1: {Name:Мой шаблон CategoryInfo:0xc00000c0c0}
    data1.CategoryInfo: &{ID:0 Name:Тест}

    data2: {Name:Мой шаблон CategoryInfo:}
    data2.CategoryInfo:
    Ответ написан
    7 комментариев
  • Проблема с отправкой сообщения, как решить?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Предполагаю вам нужно запрос в базу переместить в MessageNew.
    Т.е. алгоритм должен быть таким:
    - вы получили запрос /dump
    - сделали запрос в базу
    - в цикле извлекли данные и записали их в Buffer
    - одним сообщением ответили данными из Buffer

    Например так:
    lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) {
        // тут приходит новый запрос
        if obj.Message.Text == "/dump" {
           var str strings.Builder
           // читаете данные из базы
           res, err := db.Query("SELECT `report_id`, `content_type` FROM `xf_report` WHERE `report_state` = 'open'")
           // по одном их обрабатываете и записываете в strings.Builder
           for res.Next(){
            err := res.Scan(&report.Report_id, &report.Content_type)
            str.WriteString(...)
          }
          // тут у вас все данные собраны в str
          // отправляете данные в ответ
          send, err2 := vk.MessagesSend(api.Params{
              "peer_id": 2000000001,
              "random_id": 0,
              "message": str.String(),
            })
        }

    Я убрал из кода обработку ошибок и т.д., чтобы вам было лучше видно суть.
    Ответ написан
    6 комментариев
  • Почему не запускаются миграции?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Обычно это означает, что была попытка миграции ранее и произошла ошибка во время миграции.
    Например у вас всего одна миграция (версия 1) с созданием таблицы `users` как в вашем случае.
    Вы запускаете команду up, а в файле с миграциями ошибка.
    В это случае таблица `users` в базе не создалась, а таблицу schema_migrations команда `migrate` уже создала.
    И не просто создала, а и записала туда, что у вас база соответствует версии 1 и установила флажок `dirty`.
    SELECT * FROM schema_migrations;
    +---------+-------+
    | version | dirty |
    +---------+-------+
    |     1   |   1   |
    +---------+-------+
    1 row in set (0.00 sec)


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

    После того, как придёт понимание - нужно будет указать версию, которой соответствует база.
    migrate -path $PATH_TO_YOUR_MIGRATIONS -database $YOUR_DATABASE_URL force $VERSION

    Где $VERSION - это номер версии, которой соответствует структура базы.
    Если у вас всего одна таблица и вы уже исправили ошибку в файле миграции - можете просто удалить таблицу `schema_migrations` и дальше не читать :)

    После выполнения команды с параметрами force $VERSION флаг "dirty" будет снят и можно будет вызвать опять вашу команду с `up` и она будет успешно выполнена.
    Ответ написан
  • Можно ли после выпуска токенов довыпустить еще определённое кол-во?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Можно, как вариант, использовать что-то типа такого:
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity >=0.7.0 <0.9.0;
    
    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    
    contract YourContractName is ERC20{
        address owner;
    
        constructor() ERC20("Your token name", "YourSymbol") {
             // запоминаем кошелёк, который загружал токен, чтобы только он мог допечатывать деньги
            owner = msg.sender;
        }
        
        function printCoins(uint256 amount) public {
            require(msg.sender == owner, "Forbidden");
            _mint(owner, amount); // зачисляем деньги на кошелёк владельца контракта
        }
    }
    Ответ написан
    Комментировать
  • Как создать Dapps (децентрализованное приложение) для определённой группы лиц?

    EvgenyMamonov
    @EvgenyMamonov
    Senior software developer, system architect
    Можно заложить такой функционал в смарт-контракт.
    Например можно сделать пару функций типа grantAccess, revokeAccess, которые будут разрешать какому то кошельку (адресу) доступ к смарт контракту или отзывать разрешенный доступ.
    В этих функциях сделать проверку, чтобы их мог вызывать только владелец кошелька или как вы решите.
    А во всех остальных функциях смарт-контракта делать проверку выдавался ли ранее доступ или нет.
    Примерная реализация:
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity >=0.7.0 <0.9.0;
    
    contract ACL {
        // тут храним кошельки, которые могут выдавать/отзывать полномочия админа.
        mapping (address => bool) public admins;
    
        // тут те, кому разрешен доступ
        mapping (address => bool) public accessGranted;
    
        constructor() {
            // наделяем полномочиями админа того, кто деплоил контракт чтобы он потом мог выдать полномочия кому-то еще.
            admins[msg.sender] = true;
        }
        
        function grantAdmin(address to) public {
            require(admins[msg.sender] == true, "Forbidden");
            admins[to] = true;
        }
    
        function revokeAdmin(address to) public {
            require(admins[msg.sender] == true, "Forbidden");
            delete admins[to];
        }
    
        function grantAccess(address to) public {
            require(admins[msg.sender] == true, "Forbidden");
            accessGranted[to] = true;
        }
    
        function revokeAccess(address to) public {
            require(admins[msg.sender] == true, "Forbidden");
            delete accessGranted[to];
        }
        
        // эта функция доступна всем
        function publicOK() public pure returns (string memory) {
            return "public OK";
        }
    
        // эта доступна только тем, кому был предоставлен доступ
        function protectedOK() public view returns (string memory) {
            require(accessGranted[msg.sender] == true, "Forbidden");
            return "protected OK";
        }
    }
    Ответ написан
    4 комментария
  • Как в map добавить slice?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Нужно изменить тип map на map[string]interface{}
    Вот так
    message := map[string]interface{}{
        "cmd":       "subscribe",
        "auth_key":  "dfb496cca67e13b",
        "needed_bk": []string {"live"},
      }
    Ответ написан
    6 комментариев
  • Правильно ли делать сервис менеджер в Go?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    > В UserService может использоваться логика ProjectService'a. Для этого я передаю в userService.New(projectService ProjectService)

    По хорошему в UserService не должно быть логики ProjectService.
    Логику лучше не разносить по разным местам, такой код очень трудно потом поддерживать.
    Представьте, что через год придёт новый человек и внесёт правки в ProjectService, он может не знать/забыть внести нужные правки в UserService.

    Если без этого совсем никак, тогда лучше сделать 3й сервис, который в конструкторе примет сервисы Users и Project и будет вызывать нужные методы из каждого из них.

    В идеале нужно перестроиться и проектировать пакеты так, чтобы таких проблем не возникало.
    Но когда вы перестроитесь - результаты работы будут намного качественнее.
    Мне после Perl, Python понадобилось не мало времени чтобы перестроиться :))
    Но сейчас могу с уверенностью сказать оно того точно стоило!

    Подход как у вас с ServiceManager тоже используется, даже видел библиотеки готовые, но уже не помню как назывались. Я попробовал их, у меня не прижились эти библиотеки )

    Сейчас я использую такой подход:
    users/user.go
    тут описываю структуру Users, простая валидация полей, методы у структуры типа GetFullName и т.д.

    users/repo.go
    тут описываю интерфейсы:
    QueryRepo для извлечения данных
    CommandRepo для внесения изменения в данные пользователей
    репозитории используются только в рамках пакета users, другие пакеты используют интерфейсы сервисов

    users/querysvc.go
    тут описываю интерфейс QuerySvc (только для извлечения данных связанных с пользователей),
    он уже использует интерфейс QueryRepo
    этот пакет будут импортировать другие пакеты

    users/commandsvc.go
    тут описываю интерфейс CommandSvc (только для внесения изменений в данные пользователей), он тоже использует интерфейсы QueryRepo, CommandRepo
    этот пакет будут импортировать другие пакеты

    users/signupsvc.go
    это сервис регистрации пользователей, тут описываю интерфейс SignupSvc

    users/repos/mysql/query.go
    тут реализация интерфейса QueryRepo на MySQL
    этот пакет будет импортироваться только в пакете `app` (см. ниже)

    users/repos/mysql/command.go
    тут реализация интерфейса CommandRepo на MySQL
    этот пакет будет импортироваться только в пакете `app` (см. ниже)

    users/services/querysvc.go
    тут реализация интерфейса QuerySvc

    users/services/commandsvc.go
    тут реализация интерфейса CommandSvc

    users/services/signupsvc.go
    тут реализация интерфейса SignupSvc

    core/
    тут все константы, которые могут быть использованы в разных пакетах приложения (например коды ошибок)
    этот пакет могут включать любые пакеты, но он ничего не импортирует в рамках проекта

    app/
    тут иницализация всех репозиториев, сервисов, конфигов и т.д.
    здесь же происходит связывание всех пакетов.
    Т.е. что-то вроде
    usersQuerySvc := usersservices.NewQuerySvc(usersRepo, ...)
    usersSignupSvc := usersservices.NewSignupSvc(usersQuerySvc, usersCommandSvc)
    // например сервис для админов (с проверкой полномочий), методы которого уже можно использовать в endpoint'ax
    usersAdminSvc := usersservices.NewAdminSvc(usersQuerySvc, usersCommandSvc)
    
    projectSvc := projectservices.New(usersQuerySvc)

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

    Ну и все пакеты как параметры принимают везде только интерфейсы.
    Хотя... с точки зрения Dependency Inversion, правильно бы было описывать под каждый пакет свой интерфейс, а не так, как я :), но я делаю интерфейсы очень маленькими, тогда получается очень гибко и, как мне кажется, после этого еще внутри других пакетов описывать интерфейсы уже избыточно в моём случае, работы больше, а толку не так много, по этому пока экономлю на этом время :)

    cmd/serve.go - импортирует app и запускает веб сервер например

    Если нужны будут еще какие то подробности - пишите, буду рад помочь
    Ответ написан
    Комментировать
  • Как правильно записать ListenAndServeTLS() с кастомным типом servemux?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вы вызываете метод ListenAndServeTLS для структуры Server, у него всего два параметра.
    Четыре параметра у метода ListenAndServeTLS для пакета http.

    Вам нужно добавить параметр Handler: mux в структуру, и вызвать метод ListenAndServeTLS структуры Server. У вас должно получиться примерно так:
    func main() {
        var mux = http.NewServeMux()
        mux.HandleFunc("/", indexPage)
        var serv = &http.Server{
            // добавляете параметр
            Handler:      mux,
            Addr:         serverPort,
            ReadTimeout:  15 * time.Second,
            WriteTimeout: 15 * time.Second,
        }
        // вызываете с двумя параметрами
        log.Fatal(serv.ListenAndServeTLS(TLScert, TLSkey))
    }
    Ответ написан
  • Как корректно написать функцию getBlobReader?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Вам нужно реализовать свой io.Reader, и в Read уже вычитывать данные по очереди из частей файла.
    Вот работающий пример реализации
    package main
    
    import (
        "io"
        "log"
        "os"
        "path/filepath"
    )
    
    type BlobReader struct {
        keys       []string
        currentKey uint64
        reader     io.ReadCloser
    }
    
    func NewBlobReader(keys []string) *BlobReader {
        return &BlobReader{
            keys: keys,
        }
    }
    
    // Read реализация интерфейса io.Reader, чтобы можно было ваш reader использовать в io.Copy
    func (br *BlobReader) Read(p []byte) (int, error) {
        var err error
    
        // открываем каждый файл по очереди
        if br.reader == nil {
            filePath := filepath.Join(".", "blobs", br.keys[br.currentKey])
            br.reader, err = os.Open(filePath)
            if err != nil {
                return 0, err
            }
        }
    
        // читаем данные из текущего открытого файла, пока данные не закончатся
        n, err := br.reader.Read(p)
    
        // если данные в файле закончились, закрываем его
        if err == io.EOF {
            br.currentKey++
            br.reader.Close()
            br.reader = nil
    
            // io.EOF в err должно вернуть только у последнего файла, чтобы io.Copy считал все файлы и не завис на последнем.
            if br.currentKey < uint64(len(br.keys)) {
                err = nil
            }
        }
    
        return n, err
    }
    
    func main() {
        blobReader := NewBlobReader([]string{
            "2050-part1",
            "2050-part2",
            "2050-part3",
        })
    
        _, err := io.Copy(os.Stdout, blobReader)
        if err != nil {
            log.Fatalln(err)
        }
    
        log.Println("Done")
    }

    Еще нужно будет реализовать метод Close у вашего Reader'a чтобы небыло утечек файловых дескрипторов
    Ответ написан
    4 комментария
  • Как оповестить пачку горутин о завершении работы?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    Попробуйте вот так
    package main
    
    import (
        "context"
        "fmt"
        "sync"
    )
    
    func worker(ctx context.Context, worderID int, data chan int, wg *sync.WaitGroup) {
        defer wg.Done()
    
        fmt.Printf("worker %d started\n", worderID)
        for {
            fmt.Printf("worker %d enter for\n", worderID)
            select {
            case <-ctx.Done():
                fmt.Printf("worker %d cancelled\n", worderID)
                return
            case v, ok := <-data:
                fmt.Printf("worker %d got data: %v, ok: %v\n", worderID, v, ok)
                if !ok {
                    fmt.Printf("worker %d data channel was closed\n", worderID)
                    return
                }
            }
        }
    }
    
    func main() {
        var wg sync.WaitGroup
        ctx, cancel := context.WithCancel(context.Background())
        channels := make([]chan int, 10)
    
        for i := 0; i < 10; i++ {
            wg.Add(1)
            channels[i] = make(chan int)
            go worker(ctx, i, channels[i], &wg)
        }
    
        for i := 0; i < 10; i++ {
            channels[i] <- i
        }
    
        cancel()
        wg.Wait()
    }
    Ответ написан
  • Почему нельзя вернуть так структуру?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    В реальной жизни, скорее всего, задачу придёться решать немного иначе.
    Обычно вам приходят данные в виде структур, например, когда вы извлекаете данные из базы.
    В момент получения данных вам нужно просто заполнить слайс интерфейсов этими структурами (которые реализуют методы интерфейса)

    Сделал пример того, как задача может быть решена

    https://play.golang.org/p/RKrCLIqakXg

    package main
    
    import "fmt"
    
    // объявляем интерфейс
    type Profile interface {
    	GetFullName() string
    }
    
    // структура данных, которая будет реализовывать интерфейс Profile
    type User struct {
        Name string
        LastName string
    }
    
    // реализация метода интерфейса Profile
    func (u User) GetFullName() string {
    	return u.Name + ` ` + u.LastName
    }
    
    // пример функции, которая извлекает/формирует данные и возвращает слайс интерфейсов
    func getProfiles() []Profile {
            // создаём слайс интерфейсов
    	var result = make([]Profile, 10)
    
    	for i := 0; i<10; i++ {
    	    // но заполняем слайс структурами, которые реализуют этот интерфейс
    	    result[i] = User{
    	        Name: fmt.Sprintf("Name%d", i),
    	        LastName: fmt.Sprintf("LastName%d", i),
    	    }
    	}
    
    	return result
    }
    
    // пример функции, которая работает со слайсом интерфейсов.
    func printProfiles(profiles []Profile) {
        for _, profile := range profiles {
            fmt.Println(profile.GetFullName())
        }
    }
    
    func main() {
    	printProfiles(getProfiles())
    }
    Ответ написан
    Комментировать
  • Golang vksdk, проблема с типом данных, как решить?

    EvgenyMamonov
    @EvgenyMamonov Куратор тега Go
    Senior software developer, system architect
    вместо b.MessageIDs(obj.Message.ID) напишите b.MessageIDs([]int{obj.Message.ID})
    Ответ написан