Ich habe versucht, einen API-Server mit Go (Echo) x MySQL x Docker x Clean Architecture zu erstellen

Einführung

Wie der Titel schon sagt, habe ich mit Go x MySQL x Docker einen API-Server erstellt. am Anfang,

・ Erstellen Sie mit Go eine einfache REST-API ・ Richten Sie mit Docker eine Entwicklungsumgebung ein

Ich habe es zu dem Zweck gemacht, aber als ich mich fragte, was ich mit der Verzeichnisstruktur anfangen und verschiedene Dinge untersuchen sollte, fand ich viele Artikel, die API-Server mit Go × Clean-Architektur herstellen, und entschied mich daher, Clean Architecture auszuprobieren. Ich versuchte es.

Also dieser Artikel ist

・ Ich möchte eine API in Go schreiben ・ Ich möchte mit Docker eine Entwicklungsumgebung erstellen ・ Zu diesem Zeitpunkt möchte ich eine saubere Architektur annehmen

Empfohlen für diejenigen, die ein Gefühl von Temperatur haben.

Im Gegenteil, es ist möglicherweise nicht sehr hilfreich für diejenigen, die eine solide, saubere Architektur studieren möchten. ..

Darüber hinaus sollte beachtet werden ** Echo ** im Framework (https://echo.labstack.com/) ** Gorm ** (https://github.com/go-gorm/gorm), um auf die Datenbank zuzugreifen mit.

Klicken Sie hier für das Repository ↓

Vorbereitung

Entwicklungsumgebung

Ich habe mit Docker eine Entwicklungsumgebung erstellt.

Dockerfile Dockerfile Official Reference ist eine gute Referenz zum Schreiben einer einfachen Dockerfile.

Schauen wir sie uns jetzt kurz an.

Zuerst ist Go.

docker/api/Dockerfile


FROM golang:1.14

#benutze das go Modul
ENV GO111MODULE=on 

#Geben Sie das Verzeichnis an, in dem die Anwendung ausgeführt werden soll
WORKDIR /go/src/github.com/Le0tk0k/go-rest-api

#Gehen Sie zum obigen Verzeichnis.mod und los.Summe kopieren
COPY go.mod go.sum ./
#Wenn sich in der obigen Datei nichts ändert, kann der Cache verwendet werden
RUN go mod download

COPY . .
RUN go build .

RUN go get github.com/pilu/fresh

EXPOSE 8080

#Starten Sie den Server mit dem Befehl fresh
CMD ["fresh"]

Hot Reloading unter github.com/pilu/fresh aktiviert. Davon abgesehen ist nichts Besonderes daran.

Referenz ・ Verwenden des Go-Mod-Downloads zur Beschleunigung der Golang Docker-BuildsGo v1.11 + Docker + frisch, um eine Hot-Reload-Entwicklungsumgebung zu erstellen und ein angenehmes Go-Sprachleben zu führen

Als nächstes kommt MySQL.

docker/mysql/Dockerfile


FROM mysql

EXPOSE 3306

#Kopieren Sie die MySQL-Konfigurationsdatei in das Image
COPY ./docker/mysql/my.cnf /etc/mysql/conf.d/my.cnf

CMD ["mysqld"]

Die MySQL-Konfigurationsdatei sieht folgendermaßen aus: Da sich die Authentifizierungsmethode zum Zeitpunkt der Verbindung in MySQL 8.0 oder höher geändert hat, scheint die Beschreibung in der zweiten Zeile erforderlich zu sein. Außerdem wurde der Zeichencode geändert, um verstümmelte Zeichen zu vermeiden.

docker/mysql/my.cnf


[mysqld]
default_authentication_plugin=mysql_native_password
character-set-server=utf8mb4

[client]
default-character-set=utf8mb4

Wenn Sie die Initialisierungs-SQL und das Skript in das Verzeichnis /docker-entrypoint-initdb.d/ stellen, können die Daten beim ersten Start des Images automatisch initialisiert werden.

Erstellen wir also eine Tabelle.

docker/mysql/db/init.sql


CREATE DATABASE IF NOT EXISTS go_rest_api;
USE go_rest_api;

CREATE TABLE IF NOT EXISTS users (
  id          INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  name        VARCHAR(256) NOT NULL,
  age         INT NOT NULL,
  created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  updated_at  TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Referenz

docker-compose.yml Sie können auch auf Compose File / Official Reference verweisen.

docker-compose.yml


version: '3'
services: 
  db:
    build: 
      #Geben Sie den Pfad des Verzeichnisses an, in dem sich die Docker-Datei befindet
      context: .
      dockerfile: ./docker/mysql/Dockerfile
    ports: 
      #Geben Sie den öffentlichen Port an
      - "3306:3306"
    volumes: 
      #Daten beim Start initialisieren
      - ./docker/mysql/db:/docker-entrypoint-initdb.d
      #MySQL-Persistenz
      - ./docker/mysql/db/mysql_data:/var/lib/mysql
    environment: 
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: go_rest_api
      MYSQL_USER: user
      MYSQL_PASSWORD: password

  api:
    build:
      context: .
      dockerfile: ./docker/api/Dockerfile
    volumes: 
      - ./:/go/src/github.com/Le0tk0k/go-rest-api
    ports: 
      - "8080:8080"
    depends_on:
      #db startet zuerst
      - db

Da MySQL persistent gemacht wird, bleiben die vorherigen Daten auch dann erhalten, wenn der Container neu gestartet wird.

Wenn Sie abhängige_on für API angeben, wird zuerst db und dann api gestartet. Depend_on gibt jedoch nur die Reihenfolge des Starts an und wartet nicht, bis dieser abgeschlossen ist. Daher ist eine weitere Maßnahme erforderlich. Die Gegenmaßnahmen werden im Implementierungsabschnitt vorgestellt.

Referenz

Saubere Architektur

Clean Architecture ist eine Architektur, die in The Clean Architecture vorgeschlagen wird.

Bin ich überhaupt eine Architektur? Was das? Es war in einem Zustand, aber nachdem ich den folgenden Artikel gelesen hatte, glaube ich, dass ich ungefähr 1 mm Überstand verstanden habe. Es gibt viele andere Artikel, also schaut sie euch bitte an!

Referenz (Es gibt auch andere Artikel als saubere Architektur)

Implementierung

Verzeichnisaufbau

Die Verzeichnisstruktur ist wie folgt.


├── docker
│   ├── api
│   │   └── Dockerfile
│   └── mysql
│       ├── Dockerfile
│       ├── db
│       │   ├── init.sql
│       │   └── mysql_data
│       └── my.cnf
├── docker-compose.yml
├── domain
│   └── user.go
├── infrastructure
│   ├── router.go
│   └── sqlhandler.go
├── interfaces
│   ├── controllers
│   │   ├── context.go
│   │   ├── error.go
│   │   └── user_controller.go
│   └── database
│       ├── sql_handler.go
│       └── user_repository.go
|── usecase
│   ├── user_interactor.go
│   └── user_repository.go
├── server.go
├── go.mod
├── go.sum

Domain → Entities-Ebene Infrastruktur → Frameworks & Drivers-Ebene Schnittstellen → Schnittstellenschicht Anwendungsfall → Anwendungsfall-Ebene

Es entspricht.

Domäne (Entitätsschicht)

Die innerste Schicht, die von allem unabhängig ist. Wir werden das Benutzermodell definieren.

Für json:" - " wird beim Schreiben- keine Ausgabe ausgegeben.

domain/user.go


package domain

import "time"

type User struct {
	ID        int       `gorm:"primary_key" json:"id"`
	Name      string    `json:"name"`
	Age       int       `json:"age"`
	CreatedAt time.Time `json:"-"`
	UpdatedAt time.Time `json:"-"`
}

type Users []User

Schnittstellen / Datenbank, Infrastruktur (Schnittstellenschicht, Frameworks & Treiberschicht)

Als nächstes werden wir den Datenbankbereich implementieren. Da es sich bei der Datenbankverbindung um eine externe Verbindung handelt, definieren Sie sie in der äußersten Infrastrukturschicht.

infrastructure/sqlhandler.go


package infrastructure

import (
	"fmt"
	"time"

	"github.com/Le0tk0k/go-rest-api/interfaces/database"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

type SqlHandler struct {
	Conn *gorm.DB
}

func NewMySqlDb() database.SqlHandler {

	connectionString := fmt.Sprintf(
		"%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
		"user",
		"password",
		"db",
		"3306",
		"go_rest_api",
	)

	conn, err := open(connectionString, 30)
	if err != nil {
		panic(err)
	}

    //Überprüfen Sie, ob Sie eine Verbindung herstellen können
	err = conn.DB().Ping()
	if err != nil {
		panic(err)
	}

    //Details zum Ausgabeprotokoll
	conn.LogMode(true)
    //Richten Sie die DB-Engine ein
	conn.Set("gorm:table_options", "ENGINE=InnoDB")

	sqlHandler := new(SqlHandler)
	sqlHandler.Conn = conn

	return sqlHandler
}

//Stellen Sie sicher, dass der API-Container gestartet wird, nachdem Sie den Start von MySQL bestätigt haben
func open(path string, count uint) (*gorm.DB, error) {
	db, err := gorm.Open("mysql", path)
	if err != nil {
		if count == 0 {
			return nil, fmt.Errorf("Retry count over")
		}
		time.Sleep(time.Second)
		count--
		return open(path, count)
	}
	return db, nil
}

func (handler *SqlHandler) Find(out interface{}, where ...interface{}) *gorm.DB {
	return handler.Conn.Find(out, where...)
}

func (handler *SqlHandler) Create(value interface{}) *gorm.DB {
	return handler.Conn.Create(value)
}

func (handler *SqlHandler) Save(value interface{}) *gorm.DB {
	return handler.Conn.Save(value)
}

func (handler *SqlHandler) Delete(value interface{}) *gorm.DB {
	return handler.Conn.Delete(value)
}

In opan () wird der API-Container so eingestellt, dass er startet, nachdem auf den Start des DB-Containers gewartet wurde. Dieses Mal habe ich diese Funktion in Go-Code implementiert. Ist es jedoch besser, sie in einem Shell-Skript zu implementieren? Ich kenne die beste Praxis nicht ... Referenz

Darüber hinaus ist die Infrastrukturschicht nur eine Datenbankverbindung, und die eigentliche Verarbeitung wird in der Schnittstellen- / Datenbankschicht implementiert.

interfaces/database/user_repository.go


package database

import (
	"github.com/Le0tk0k/go-rest-api/domain"
)

type UserRepository struct {
	SqlHandler
}

func (userRepository *UserRepository) FindByID(id int) (user domain.User, err error) {
	if err = userRepository.Find(&user, id).Error; err != nil {
		return
	}
	return
}

func (userRepository *UserRepository) Store(u domain.User) (user domain.User, err error) {
	if err = userRepository.Create(&u).Error; err != nil {
		return
	}
	user = u
	return
}

func (userRepository *UserRepository) Update(u domain.User) (user domain.User, err error) {
	if err = userRepository.Save(&u).Error; err != nil {
		return
	}
	user = u
	return
}

func (userRepository *UserRepository) DeleteByID(user domain.User) (err error) {
	if err = userRepository.Delete(&user).Error; err != nil {
		return
	}
	return
}

func (userRepository *UserRepository) FindAll() (users domain.Users, err error) {
	if err = userRepository.Find(&users).Error; err != nil {
		return
	}
	return
}

Ich binde einen SqlHandler in das UserRepository ein. Ich importiere die Domänenschicht, die die innerste Schicht ist, damit die Abhängigkeiten erhalten bleiben.

Da SqlHandler jedoch in der äußersten Infrastrukturschicht definiert ist, werden die Abhängigkeiten von innen nach außen gerichtet, und die Regeln werden möglicherweise nicht befolgt. Der hier aufgerufene SqlHandler ist jedoch keine in der Infrastrukturschicht definierte Struktur, sondern eine in der Schnittstellen- / Datenbankschicht definierte Schnittstelle, die dieselbe Schicht ist. Dies wird als ** DIP (Dependency Reversal Principle) ** bezeichnet.

Wenn Sie in interfaces / database / user_repository.go den in der Infrastructure-Schicht definierten Prozess aufrufen, ändert sich die Abhängigkeit von innen nach außen, und Sie können die Abhängigkeit nicht schützen. Um dies zu vermeiden, definieren wir die Interaktion mit DB in derselben Ebene mit der Schnittstelle und rufen sie auf. Obwohl die Verarbeitung tatsächlich in der Infrastrukturschicht ausgeführt wird, ist die Abhängigkeit dadurch geschützt, da die Schnittstelle / database / user_repository.go die SqlHandler-Schnittstelle aufruft.

Und in Go, wenn ** Typ T alle in Schnittstelle I definierten Methoden hat, bedeutet dies, dass Schnittstelle I implementiert ist **, also die SqlHandler-Schnittstelle von interfaces / database / sql_hander in der Infrastructure.SqlHandler-Struktur. Definiert die in definierte Methode.

interfaces/database/sql_handler.go


package database

import "github.com/jinzhu/gorm"

type SqlHandler interface {
	Find(interface{}, ...interface{}) *gorm.DB
	Create(interface{}) *gorm.DB
	Save(interface{}) *gorm.DB
	Delete(interface{}) *gorm.DB
}

Usecase (Anwendungsfall-Ebene)

Die Usecase-Schicht ist dafür verantwortlich, Informationen von der Schnittstellen- / Datenbankschicht zu empfangen und an die Schnittstellen- / Controller-Schicht weiterzuleiten.

usecase/user_interactor.go


package usecase

import "github.com/Le0tk0k/go-rest-api/domain"

type UserInteractor struct {
	UserRepository UserRepository
}

func (interactor *UserInteractor) UserById(id int) (user domain.User, err error) {
	user, err = interactor.UserRepository.FindByID(id)
	return
}

func (interactor *UserInteractor) Users() (users domain.Users, err error) {
	users, err = interactor.UserRepository.FindAll()
	return
}

func (interactor *UserInteractor) Add(u domain.User) (user domain.User, err error) {
	user, err = interactor.UserRepository.Store(u)
	return
}

func (interactor *UserInteractor) Update(u domain.User) (user domain.User, err error) {
	user, err = interactor.UserRepository.Update(u)
	return
}

func (interactor *UserInteractor) DeleteById(user domain.User) (err error) {
	err = interactor.UserRepository.DeleteByID(user)
	return
}

UserInteractor ruft UserRepository auf, aber wenn Sie es von der Interfase-Ebene aus aufrufen, hängt es von innen → außen ab und Sie können die Abhängigkeit nicht schützen. Erstellen Sie also user_repository.go in derselben Ebene und implementieren Sie die Schnittstelle * Schützen Sie Abhängigkeiten mit * DIP (Dependency Reversal Principle) **.

usecase/user_repository.go


package usecase

import "github.com/Le0tk0k/go-rest-api/domain"

type UserRepository interface {
	FindByID(id int) (domain.User, error)
	Store(domain.User) (domain.User, error)
	Update(domain.User) (domain.User, error)
	DeleteByID(domain.User) error
	FindAll() (domain.Users, error)
}

Schnittstellen / Controller, Infrastruktur (Schnittstellenschicht, Frameworks & Treiberschicht)

Dann werden wir den Controller und den Router implementieren.

interfaces/controllers/user_controller.go


package controllers

import (
	"strconv"

	"github.com/Le0tk0k/go-rest-api/domain"
	"github.com/Le0tk0k/go-rest-api/interfaces/database"
	"github.com/Le0tk0k/go-rest-api/usecase"
)

type UserController struct {
	Interactor usecase.UserInteractor
}

func NewUserController(sqlHandler database.SqlHandler) *UserController {
	return &UserController{
		Interactor: usecase.UserInteractor{
			UserRepository: &database.UserRepository{
				SqlHandler: sqlHandler,
			},
		},
	}
}

func (controller *UserController) CreateUser(c Context) (err error) {
	u := domain.User{}
	c.Bind(&u)
	user, err := controller.Interactor.Add(u)

	if err != nil {
		c.JSON(500, NewError(err))
		return
	}
	c.JSON(201, user)
	return
}

func (controller *UserController) GetUsers(c Context) (err error) {
	users, err := controller.Interactor.Users()

	if err != nil {
		c.JSON(500, NewError(err))
		return
	}
	c.JSON(200, users)
	return
}

func (controller *UserController) GetUser(c Context) (err error) {
	id, _ := strconv.Atoi(c.Param("id"))
	user, err := controller.Interactor.UserById(id)

	if err != nil {
		c.JSON(500, NewError(err))
		return
	}
	c.JSON(200, user)
	return
}

func (controller *UserController) UpdateUser(c Context) (err error) {
	id, _ := strconv.Atoi(c.Param("id"))
	u := domain.User{ID: id}
	c.Bind(&u)

	user, err := controller.Interactor.Update(u)

	if err != nil {
		c.JSON(500, NewError(err))
		return
	}
	c.JSON(201, user)
	return
}

func (controller *UserController) DeleteUser(c Context) (err error) {
	id, _ := strconv.Atoi(c.Param("id"))
	user := domain.User{ID: id}

	err = controller.Interactor.DeleteById(user)
	if err != nil {
		c.JSON(500, NewError(err))
		return
	}
	c.JSON(200, user)
	return
}

Da echo echo.Context verwendet, definieren Sie die Schnittstelle der diesmal verwendeten Methode.

interfaces/controllers/context.go


package controllers

type Context interface {
	Param(string) string
	Bind(interface{}) error
	JSON(int, interface{}) error
}

interfaces/controllers/error.go


package controllers

type Error struct {
	Message string
}

func NewError(err error) *Error {
	return &Error{
		Message: err.Error(),
	}
}

Da das Routing Echo verwendet (mithilfe eines externen Pakets), implementieren Sie es in der Infrastrukturschicht. Der Typ echo.Context kann als Argument im uesrController übergeben werden. Funktion (c), da echo.Context die Kontextschnittstelle von interfaces / controller / context.go erfüllt. (Das heißt, der Typ echo.Context verfügt über alle Methoden der Kontextschnittstelle.)

infrastructure/router.go


package infrastructure

import (
	"github.com/Le0tk0k/go-rest-api/interfaces/controllers"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

func Init() {
	e := echo.New()

	userController := controllers.NewUserController(NewMySqlDb())

    //Ausgabeanforderungsbasierte Protokolle wie Zugriffsprotokolle ausgeben
	e.Use(middleware.Logger())
    //Wenn Sie versehentlich irgendwo in Ihrer Anwendung in Panik geraten, wird der Server wiederhergestellt, sodass er eine Fehlerantwort zurückgeben kann, ohne sie zu löschen.
	e.Use(middleware.Recover())

	e.GET("/users", func(c echo.Context) error { return userController.GetUsers(c) })
	e.GET("/users/:id", func(c echo.Context) error { return userController.GetUser(c) })
	e.POST("/users", func(c echo.Context) error { return userController.CreateUser(c) })
	e.PUT("/users/:id", func(c echo.Context) error { return userController.UpdateUser(c) })
	e.DELETE("/users/:id", func(c echo.Context) error { return userController.DeleteUser(c) })

	e.Logger.Fatal(e.Start(":8080"))
}

Rufen Sie es schließlich von server.go auf.

server.go


package main

import "github.com/Le0tk0k/go-rest-api/infrastructure"

func main() {
	infrastructure.Init()
}

Ausführung

Lassen Sie uns nun die fertige API treffen. Diesmal möchte ich ** Postman ** verwenden. (https://www.postman.com/downloads/)

Setzen Sie zunächst den Inhaltstyp der Header auf application / json. スクリーンショット 2020-09-19 16.09.22.png

GetUsers Versuchen Sie, alle Benutzer zu bekommen. Senden Sie eine GET-Anfrage an / users. スクリーンショット 2020-09-19 16.14.07.png

Getuser Versuchen Sie, einen Benutzer zu finden. Senden Sie eine GET-Anfrage an / users /: id. スクリーンショット 2020-09-19 16.16.59.png

CreateUser Zuerst erstellen wir einen Benutzer. Senden Sie eine POST-Anfrage an / users. スクリーンショット 2020-09-19 16.11.47.png

UpdateUser Versuchen Sie, einen beliebigen Benutzer zu aktualisieren. Senden Sie eine PUT-Anfrage an / users /: id. スクリーンショット 2020-09-19 16.18.02.png

DeleteUser Versuchen Sie, einen beliebigen Benutzer zu löschen. Senden Sie eine DELETE-Anfrage an / users /: id. スクリーンショット 2020-09-19 16.19.14.png

Wenn ich es mit GET überprüfe, wird es fest gelöscht. スクリーンショット 2020-09-19 16.19.24.png

Es funktioniert gut!

schließlich

Ich hätte einfach eine Entwicklungsumgebung mit Docker einrichten und eine REST-API erstellen sollen, aber es war schwierig, saubere Architektur zu studieren, aber ich denke, es war eine großartige Gelegenheit, die Architektur kennenzulernen. Im Gegenteil, mit einer kleinen API wie dieser scheint es kompliziert zu sein, aber ich hatte den Eindruck, dass es gut wäre, wenn es ein großer Maßstab wäre. (~~ Eigentlich verstehe ich nicht viel ~~) Ich würde gerne wieder Design studieren!

Recommended Posts

Ich habe versucht, einen API-Server mit Go (Echo) x MySQL x Docker x Clean Architecture zu erstellen
Ich habe versucht, mit Docker eine Plant UML Server-Umgebung zu erstellen
Build Rails (API) x MySQL x Nuxt.js Umgebung mit Docker
Ich habe versucht, den Chat mit dem Minecraft-Server mit der Discord-API zu verknüpfen
Ich habe versucht, mit Eclipse + Tomcat eine http2-Entwicklungsumgebung zu erstellen
Ich habe versucht, AdoptOpenJDK 11 (11.0.2) mit dem Docker-Image zu überprüfen
[Rails] So erstellen Sie eine Umgebung mit Docker
So erstellen Sie mit Docker ~ Vue ~ eine [TypeScript + Vue + Express + MySQL] -Umgebung
Ich habe MySQL 5.7 mit Docker-Compose gestartet und versucht, eine Verbindung herzustellen
Ich habe versucht, Animationen mit der Blazor + Canvas-API zu zeichnen
Rails6 Ich habe versucht, Docker in eine vorhandene Anwendung einzuführen
So erstellen Sie mit Docker ~ Express ~ eine [TypeScript + Vue + Express + MySQL] -Umgebung
Ich habe versucht, die Umgebung nach und nach mit Docker aufzubauen
Ich habe versucht, eine Umgebung mit WSL2 + Docker + VSCode zu erstellen
Ich habe BIND mit Docker ausprobiert
Ich habe versucht, mit Docker eine Padrino-Entwicklungsumgebung zu erstellen
02. Ich habe eine API erstellt, um eine Verbindung von Spring Boot zu MySQL (My Batis) herzustellen.
Ich habe versucht, mithilfe von JDBC Template mit Spring MVC eine Verbindung zu MySQL herzustellen
So erstellen Sie mit Docker ~ MySQL ~ eine [TypeScript + Vue + Express + MySQL] -Umgebung
Ich habe versucht, eine Android-Anwendung mit MVC zu erstellen (Java)
Ich habe versucht, den Betrieb des gRPC-Servers mit grpcurl zu überprüfen
So erstellen Sie eine Rails + Vue + MySQL-Umgebung mit Docker [neueste Version 2020/09]
So erstellen Sie mit Docker ~ Sequelize ~ eine [TypeScript + Vue + Express + MySQL] -Umgebung
Ich habe versucht, mit Java zu interagieren
Aktualisieren Sie MySQL mit Docker von 5.7 auf 8.0
Erstellen Sie mit Docker eine Umgebung für "API-Entwicklung + API-Überprüfung mithilfe der Swagger-Benutzeroberfläche"
Ich habe versucht, eine Web-API zu erstellen, die mit Quarkus eine Verbindung zur Datenbank herstellt
So erstellen Sie eine Ruby on Rails-Entwicklungsumgebung mit Docker (Rails 6.x)
So erstellen Sie eine Ruby on Rails-Entwicklungsumgebung mit Docker (Rails 5.x)
Immerhin wollte ich den Inhalt von MySQL mit Docker in der Vorschau anzeigen ...
Ich habe versucht, mit Web Assembly zu beginnen
Ich habe versucht, Scalar DL mit Docker zu verwenden
Mit Docker auf Heroku bereitstellen (Rails 6, MySQL)
Ich habe einen Öko-Server mit Scala gemacht
Erstellen Sie mit Docker einen Authentifizierungs-Proxyserver
Ich habe versucht, AdoptOpenjdk 11 unter CentOS 7 zu erstellen
Was ist Docker? Ich habe versucht zusammenzufassen
Implementieren Sie eine einfache CRUD mit Go + MySQL + Docker
Erstellen Sie eine Umgebung mit Docker unter AWS
So erstellen Sie eine Rails 6-Umgebung mit Docker
Ich habe versucht, ein Portfolio mit AWS, Docker, CircleCI, Laravel [mit Referenzlink] zu erstellen.
Ich habe eine App für maschinelles Lernen mit Dash (+ Docker) Teil 3 ~ Übung ~ erstellt
[Erste Umgebungskonstruktion] Ich habe versucht, eine Rails6 + MySQL8.0 + Docker-Umgebung unter Windows 10 zu erstellen.
[Java] Ich habe versucht, über den Verbindungspool eine Verbindung mit Servlet (Tomcat) & MySQL & Java herzustellen
[Fehlerbehebung] Tritt auf, wenn versucht wird, mit Docker eine Umgebung für den Frühling zu erstellen
Ich habe versucht, eine Standardauthentifizierung mit Java durchzuführen
Ich habe versucht, die Federbeinkonfiguration mit Coggle zu verwalten
Ich habe versucht, Anmeldeinformationen mit JMX zu verwalten
Ich habe versucht, AdoptOpenJDK 8 zu erstellen (zusätzlich: Amazon Corretto 8)
So erstellen Sie eine API mit GraphQL und Rails