- 投稿日:2020-10-25T22:52:07+09:00
.NET Core + MySQL + Elasticsearch(Nest)構成でクリーンアーキテクチャなバックエンド開発
VSCode Remote Containersで.NET Core + MySQL + Elasticsearchの開発環境構築 の続きです。
今回はアプリケーションコードの開発についてご紹介しようと思います。
はじめに
まず、今回構成するアプリケーションについてですが、最小限の構成で作るので要件は以下とします。
- データの一覧が表示可能
- データの登録が可能
- データの全文検索が可能
以上の要件を満たすアプリケーションを作りたいので、今回はエンティティを本と定義し、本棚アプリケーションを作成していきたいと思います。
また、今回デフォルトでDIコンテナをサポートしている.NET Coreを採用しているということで、バックエンドの部分に関してクリーンアーキテクチャライクに作ってみました。
環境
- MacBook Pro 10.15.7
- VSCode 1.50.1
- Docker 19.03.13
- Docker For Mac 2.4.0.0
- Docker image version
- mysql:8.0
- sebp/elk:oss-792
- mcr.microsoft.com/dotnet/core/sdk:3.1
あと、VSCodeのExtensionであるRemote-Containersを使用しています
ソースコード
https://github.com/t-ash0410/asp.net-core-sample
大雑把なディレクトリ構成
root/
├ .devcontainer/
└ 省略
├ app/
├ Lib/
├ Books/
├ Entity / BookOverview.cs
├ Repository
├ BookRepository.cs
└ BookSearchRepository.cs
└ UseCase / BookInteractor.cs
└ Infrastructure/
├ DBContext.cs
└ ESContext.cs
├ Web/
├ Controllers/
├ BooksController.cs
└ 省略
├ Filters / DataAccessFilter.cs
├ 省略
└ Startup.csEntity
BookOverview.csnamespace Lib.Books.Entity { public class BookOverview { public int Id { get; set; } public string Name { get; } public string Description { get; } public string Category { get; } public BookOverview(int id, string name, string description, string category) { this.Id = id; this.Name = name; this.Description = description; this.Category = category; } } }なにはともあれEntityの定義です。
今回はサンプルアプリケーションの意味合いが強いのでElasticsearchとDBの両方に同じ形式のデータを登録することを念頭にこの構成としました。
こちらはあくまで本の概要を表すオブジェクトとして作成したので、おそらくDBには他にも以下のような本に対する詳細が存在しているのが自然だと思います。
・著者情報
・価格
・表紙画像を表すCDNパス
理想は全文検索で引っ掛けたい内容のみのElasticsearch用オブジェクトと、詳細を詰め込んだDB用オブジェクトを作ることかなと考えてます。Repository 1.MySQL
BookRepository.csusing System.Collections.Generic; using Lib.Books.Entity; using Lib.Infrastructure; namespace Lib.Books.Repository { public interface IBookRepository { void Init(); IEnumerable<BookOverview> GetBookOverviews(); void Register(BookOverview book); } public class BookRepository : IBookRepository { private readonly DBContext _ctx; public BookRepository(DBContext ctx) { this._ctx = ctx; } public void Init() { this._ctx.Command.CommandText = $@" DROP TABLE IF EXISTS books; CREATE TABLE IF NOT EXISTS books ( `id` MEDIUMINT NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) NOT NULL, `description` TEXT NOT NULL, `category` TEXT NOT NULL, PRIMARY KEY (id) );"; this._ctx.Command.ExecuteNonQuery(); } public IEnumerable<BookOverview> GetBookOverviews() { var result = new List<BookOverview>(); this._ctx.Command.CommandText = "SELECT * FROM books"; using (var reader = this._ctx.Command.ExecuteReader()) { while (reader.Read()) { var id = int.Parse(reader["id"].ToString()); var name = reader["name"].ToString(); var description = reader["description"].ToString(); var category = reader["category"].ToString(); result.Add(new BookOverview(id, name, description, category)); } } return result; } public void Register(BookOverview book) { this._ctx.Command.CommandText = $@" INSERT INTO books ( `name`, `description`, `category` ) VALUES ( @name, @description, @category )"; this._ctx.Command.Parameters.AddWithValue("@name", book.Name); this._ctx.Command.Parameters.AddWithValue("@description", book.Description); this._ctx.Command.Parameters.AddWithValue("@category", book.Category); this._ctx.Command.ExecuteNonQuery(); this._ctx.Command.Parameters.Clear(); this._ctx.Command.CommandText = "SELECT last_insert_id();"; book.Id = int.Parse(this._ctx.Command.ExecuteScalar().ToString()); } } }次にMySQLに関するRepositoryのクラスです。
こちらはDBアクセスを想定して作成されたRepositoryで、主に永続化されたデータへの操作に対する責務を担っています。
UseCaseの部分でも紹介しますが、IBookRepositoryを定義し参照をそこに限定することでDIP(依存関係逆転の法則)に従います。
また、他の特徴としてはコネクションのオープン・クローズをRepositoryの責務としていません。そこについては以下に記事を書いたので疑問を感じた方はご覧いただければと思います。
【ASP.NET】マルチテナントサービスにおけるデータベースアクセスのDIRepository 2.Elasticsearch
BookSearchRepository.csusing System.Linq; using System.Collections.Generic; using Lib.Books.Entity; using Lib.Infrastructure; namespace Lib.Books.Repository { public interface IBookSearchRepository { void Init(); IEnumerable<BookOverview> Search(string word); void Register(BookOverview book); } public class BookSearchRepository : IBookSearchRepository { private readonly ESContext _ctx; public BookSearchRepository(ESContext ctx) { this._ctx = ctx; } public void Init() { var name = "books"; if (this._ctx.Client.Indices.Exists(name).Exists) { this._ctx.Client.Indices.Delete(name); } this._ctx.Client.Indices.Create("books", index => index.Map<BookOverview>(m => m.AutoMap())); } public IEnumerable<BookOverview> Search(string word) { var res = this._ctx.Client.Search<BookOverview>((s => s .Query(q => q .Bool(b => b .Should( s => s.Match(m => m .Field(f => f.Name) .Query(word) ), s => s.Match(m => m .Field(f => f.Description) .Query(word) ), s => s.Match(m => m .Field(f => f.Category) .Query(word) ) ) .MinimumShouldMatch(1) ) ) )); return res.Hits.Select(_ => _.Source); } public void Register(BookOverview book) { this._ctx.Client.IndexDocument(book); } } }次にElasticsearchに関するRepositoryのクラスです。
こちらは全文検索データに対する操作の責務を担います。Searchメソッドについてですが、今回は検索語が名前、説明、分類のいずれかにヒットした場合はその検索結果全てを返却するような実装になっています。
また、こちらもIBookSearchRepositoryを定義し参照をそこに限定する構成にしているので、後で「やっぱCloudsearch使いたいなぁ」というときなどにBookSearchRepositoryの内容のみ変更すれば対応できるわけです。
UseCase
BookInteractor.csusing System.Collections.Generic; using Lib.Books.Entity; using Lib.Books.Repository; namespace Lib.Books.UseCase { public interface IBookInteractor { IEnumerable<BookOverview> Init(); IEnumerable<BookOverview> GetBookOverviews(); IEnumerable<BookOverview> Search(string word); IEnumerable<BookOverview> Add(string name, string description, string category); } public class BookInteractor : IBookInteractor { private readonly IBookRepository _repo; private readonly IBookSearchRepository _searchRepository; public BookInteractor(IBookRepository repo, IBookSearchRepository search) { this._repo = repo; this._searchRepository = search; } public IEnumerable<BookOverview> Init() { this._repo.Init(); this._searchRepository.Init(); var initialData = new List<BookOverview>(){ new BookOverview(1, "book1", "test book", "category1"), new BookOverview(2, "book2", "test book2", "分類2"), new BookOverview(3, "本3", "テスト用の本", "category1") }; initialData.ForEach(b => { this._repo.Register(b); this._searchRepository.Register(b); }); return initialData; } public IEnumerable<BookOverview> GetBookOverviews() { return this._repo.GetBookOverviews(); } public IEnumerable<BookOverview> Search(string word) { return this._searchRepository.Search(word); } public IEnumerable<BookOverview> Add(string name, string description, string category) { var entity = new BookOverview(-1, name, description, category); this._repo.Register(entity); this._searchRepository.Register(entity); return this._repo.GetBookOverviews(); } } }次はUseCase部分のクラスです。主にビジネスロジックを形成する責務を担います。
実はこの部分の実装については自分でもやや懐疑的で、開発チームで意見を交わしながら構築していくことが求められるのではないかと思っています。
理由は主に以下です。
- .NET Core MVCにおいてプレゼンターは必要か?
- 凝集性を高めるためにはInteractorで纏めず、メソッド単位でクラスを作成する必要があるのでは?
- インターフェースを定義しているが、Interactorを参照するのはControllerであり、同心円で考えた場合、依存関係的に必要ないのでは?
Infrastructure 1.DBContext.cs
DBContext.csusing System; using MySql.Data.MySqlClient; namespace Lib.Infrastructure { public class DBContext { private string _connectionString; public MySqlConnection Connection { get; private set; } public MySqlCommand Command { get; private set; } public DBContext(string connectionString) { this._connectionString = connectionString; } public void Open() { try { this.Connection = new MySqlConnection() { ConnectionString = this._connectionString }; this.Connection.Open(); this.Command = this.Connection.CreateCommand(); } catch { this.Close(); throw; } } public void Close() { if (this.Command != null) { this.Command.Dispose(); } if (this.Connection != null) { this.Connection.Dispose(); } } } }次はMySQLへの接続に使用するクラスです。ライブラリにはMySql.Data(version = 8.0.21)を使用しています。
この部分は直接外部サービスとのやり取りを行うため、別オブジェクトとして定義しています。
また、理由はそれ以外にも、複数レポジトリにまたがるビジネスロジックの存在や、トランザクションに対応するため等様々あり、クリーンアーキテクチャのようなデータ永続化とビジネスロジックを切り分ける実装にする場合はおそらく後悔の少ない実装方法であると思います。Infrastructure 2.ESContext.cs
ESContext.csusing System; using Nest; namespace Lib.Infrastructure { public class ESContext { public ElasticClient Client { get; } public ESContext(string url) { var uri = new Uri(url); var setting = new ConnectionSettings(uri) .DefaultMappingFor<Lib.Books.Entity.BookOverview>(map => map.IndexName("books")); this.Client = new ElasticClient(setting); } } }次はElasticsearchへの接続に使用するクラスです。ライブラリにはNEST(version = 7.9.0)を使用しています。
実装をRepositoryから切り離した理由としては上記のDBContext.csの説明と同様です。
コンストラクタ内でElasticClientオブジェクトを生成していますが、APIを叩く処理がなければ通信は発生せず、生成コストも高くない認識なのでそうしています。Controller
BooksController.csusing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Lib.Books.UseCase; using Web.Filters; namespace Web.Controllers { public class BooksController : Controller { private readonly ILogger<HomeController> _logger; private readonly IBookInteractor _interactor; public BooksController(ILogger<HomeController> logger, IBookInteractor interactor) { this._logger = logger; this._interactor = interactor; } [HttpPost] [ServiceFilter(typeof(DataAccessFilterBase))] public IActionResult Init() { var books = this._interactor.Init(); return new JsonResult(books); } [HttpGet] [ServiceFilter(typeof(DataAccessFilterBase))] public IActionResult Get() { var books = this._interactor.GetBookOverviews(); return new JsonResult(books); } [HttpGet] public IActionResult Search(string word) { var books = this._interactor.Search(word); this._logger.LogInformation($"検索結果 Search word:{word}"); return new JsonResult(books); } [HttpPost] [ServiceFilter(typeof(DataAccessFilterBase))] public IActionResult Add(string name, string description, string category) { var books = this._interactor.Add(name, description, category); return new JsonResult(books); } } }次はBookユースケースに対するControllerクラスです。
受け取った要求をinteractorに流し、JSON形式でのレスポンスを返却すること、また、ロギングの責務を担います。
[ServiceFilter(typeof(DataAccessFilterBase))]
がついているメソッドに関しては、DBアクセスを使用することを意味しています。ここの実装に関してはFilterで説明します。Filter
DataAccessFilter.cs.csusing Microsoft.AspNetCore.Mvc.Filters; using Lib.Infrastructure; namespace Web.Filters { public abstract class DataAccessFilterBase : ActionFilterAttribute { } public class DataAccessFilter : DataAccessFilterBase { private readonly DBContext _ctx; public DataAccessFilter(DBContext ctx) { this._ctx = ctx; } public override void OnActionExecuting(ActionExecutingContext context) { this._ctx.Open(); } public override void OnActionExecuted(ActionExecutedContext context) { this._ctx.Close(); } } }次はControllerに干渉するFilterクラスです。
今回定義したのはMySQLへの接続が必要な場合に関する処理で、.NET Coreのリクエストライフサイクルに直接干渉するコードをFilterで提供することによって、同じコードを複数書くことを避けています。
Controllerに直接記述することも可能ですが、そうした場合はControllerが増えた際に同じコードを書かなければならなくなります。EntryPoint 1.StartUp
Startup.csusing System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace Web { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); var connectionString = this.Configuration.GetValue<string>("DB_CONNECTION_STRING"); var searchUrl = this.Configuration.GetValue<string>("ELASTIC_SEARCH_SERVER"); services.AddScoped<Lib.Infrastructure.DBContext>((s) => new Lib.Infrastructure.DBContext(connectionString)); services.AddScoped<Lib.Infrastructure.ESContext>((s) => new Lib.Infrastructure.ESContext(searchUrl)); services.AddScoped<Lib.Books.Repository.IBookRepository, Lib.Books.Repository.BookRepository>(); services.AddScoped<Lib.Books.Repository.IBookSearchRepository, Lib.Books.Repository.BookSearchRepository>(); services.AddScoped<Lib.Books.UseCase.IBookInteractor, Lib.Books.UseCase.BookInteractor>(); services.AddScoped<Filters.DataAccessFilterBase, Filters.DataAccessFilter>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } }次に、アプリケーションのセットアップを担当するStartupクラスです。
Twelve-Factor Appに習って各接続情報などを環境変数から呼び出し、これまでご紹介したContext、Repository、Interactor、FilterをDIしていきます。
Configureに関しては開発環境で動けば良いので最小構成にしました。EntryPoint 2.Program
Program.csusing System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NLog.Web; namespace Web { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration(cfg => { cfg.AddEnvironmentVariables(); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .UseNLog(); } }最後に、今回のアプリケーションのエントリーポイントであるProgramクラスです。
特徴としてはConfigureAppConfiguration
をcallし、その中でcfg.AddEnvironmentVariables()
を実行しています。
今回のアプリケーションでは環境変数を使用してDBやESへの接続情報を制御しているので必要な実装となっています。実行
$ cd /root/app/Web
$ dotnet runこんな感じのサイトが出来上がりました。
一応本の追加と検索を実行してますがめちゃくちゃわかりづらい個性的なgifですね。
link
公式ドキュメント
ASP.NET Core MVC の概要
Elasticsearch.Net and NEST参考にさせていただいた記事
実装クリーンアーキテクチャ
NEST Tips - Elasticsearch .NET Client次回
- 投稿日:2020-10-25T21:32:27+09:00
Laravel:DB(Sequel Pro)をつなげてmigrateする方法
【概要】
1.結論
2.どのように記載するか
3.開発環境
補足
1.結論
create databaseしたファイル名と.envとconfig/database.phpでのファイル名等を一致させ、php artisan make:migrationしてから必要な記述をしphp artisan migrateする!
2.どのように記載するか
MysqlやSequel Proの導入は省きます。
❶まずデータベースを作成します
$ mysql -u root -p #パスワードを何も設定してなければEnterで進めます。 mysql> create database ファイル名;❷.env.exampleとconfig/database.phpでDB(Sequel Pro)をつなげます。
.envDB_CONNECTION=mysql #mysqlを使用しているのでこのように記載します。 DB_HOST=127.0.0.1 #データーベースサーバーのホストの指定です。 DB_PORT=3306 #初期値がこの値なのでいじらないでOKです。そうでない場合はこの値にしてください。 DB_DATABASE= #create database ファイル名;のファイル名を記載してください。 DB_USERNAME=root #Sequel Proのユーザー名を記載してください。 DB_PASSWORD= #パスワードを設定していなければ空欄でOKです。config/database.php'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', ''), 'username' => env('DB_USERNAME', 'root'), # .env.example と一致させます。❸php artisan make:migrationして、php artisan migrateします。
$ php artisan make:migration ファイル名php artisan make:migration ファイル名を記載するとファイルが生成されるので必要なカラム名や型を設定します。
#省略 public function up() { Schema::create('people', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('mail'); $table->integer('age'); $table->timestamps(); }); } #省略ここでphp artisan migrateするとcreate databaseしたDBのファイル名に反映されます。
参考にさせていただいたURL:
❶DBに反映:LaravelでSequel Proを使ったMySQLのデータベース接続(Mac/初心者向き)
3.開発環境
PHP 7.4.10
Laravel 8.9
Apache 2.4.41
Mysql 5.6.47
Sequl Pro 1.1.2
補足
しかし、2.❸でphp artisan migrateしたところ、エラーが生じ、SQLSTATE[HY000] [1049] Unknown database 'laravel'とでました。
結論から言うと、"DB_DATABASE=Laravel"のデフォルトの記述のままでした。その場所はどこかというと、下記で入力した部分でした。
$ vi .envここに記載してあるDB_DATABASE= が"Laravel"のままで自分が.env.exampleで設定した名前と違っていたので直したところDBに反映できました。自分が記載した.envファイルは.env.exampleファイルに記載しており"vi .env"に記載していなかったためです。
参考にさせていただいたURL:
❷エラーについて:Laravel MySQL SQLSTATE[HY000] [1049] Unknown database が出た時に確認すること
- 投稿日:2020-10-25T05:22:29+09:00
VSCode Remote Containersで.NET Core + MySQL + Elasticsearchの開発環境構築
現在参画しているプロジェクトにて、全文検索機能の要件があったため、既存の構成+Elasticsearchの環境構築をしてみました。
今回は開発環境構築の部分についてご紹介しようと思います。はじめに
今回ご紹介するのはあくまで開発環境用の最小構築であり、運用環境の構築にそのまま転用できる内容ではないことを予めご了承ください。
環境
- MacBook Pro 10.15.7
- VSCode 1.50.1
- Docker 19.03.13
- Docker For Mac 2.4.0.0
- Docker image version
- mysql:8.0
- sebp/elk:oss-792
- mcr.microsoft.com/dotnet/core/sdk:3.1
ソースコード
https://github.com/t-ash0410/asp.net-core-sample
ディレクトリ構成
root/
├ .devcontainer/
├ db/
├ init/
└ create_db.sql
└ my.conf
├ devcontainer.json
└ docker-compose.ymlcreate_db.sqlとmy.confに関しての説明は割愛します。
docker-compose.yml
docker-compose.ymlversion: '3' services: dotnet_db: image: mysql:8.0 container_name: dotnet_db ports: - "3306:3306" environment: MYSQL_USER: root MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: password volumes: - "db_data:/var/lib/mysql" - "./db/my.conf:/etc/mysql/my.conf" command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci --skip-character-set-client-handshake dotnet_elk: image: sebp/elk:oss-792 container_name: dotnet_elk ports: - "5601:5601" - "9200:9200" dotnet_web: image: mcr.microsoft.com/dotnet/core/sdk:3.1 container_name: dotnet_web ports: - "8080:8080" environment: DB_CONNECTION_STRING: "server=dotnet_db;port=3306;uid=root;pwd=password;database=asp_net_sample" ELASTIC_SEARCH_SERVER: "http://dotnet_elk:9200" volumes: - "../:/dotnet-app" command: /bin/bash -c "while sleep 1000; do :; done" volumes: db_data: driver: local各項目について
- image
- 作成するコンテナのイメージを指定
- container_name
- 作成するコンテナ名を指定
- ports
- 公開するポートを指定
今回の形式はホストマシンのポート:コンテナのポート
での指定となります。
- environment
- 環境変数の宣言
- volumes
- ボリュームのマウント
相対パス:絶対パス
の形式は、ホストマシン上の相対パスをコンテナ上の絶対パスにマウントしています。
この場合に指定する相対パスはdocker-compose.ymlが配置してあるディレクトリからの相対パスである必要があります。
volumesに定義したkey:絶対パス
の形式は、volumesに定義されたkeyに該当するdata volumeをコンテナ上の絶対パスにマウントしています。
- command
- コンテナのセットアップ完了後に実行されるコマンドを指定
devcontainer.json
devcontainer.json{ "name": "books app", "dockerComposeFile": [ "docker-compose.yml" ], "workspaceFolder": "/dotnet-app", "service": "dotnet_web", "extensions": [ "ms-dotnettools.csharp", "jmrog.vscode-nuget-package-manager", "ms-azuretools.vscode-docker", "p1c2u.docker-compose" ], "settings": { "editor.tabSize": 2, "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "terminal.integrated.shell.linux": "/bin/bash" }, "shutdownAction": "none" }各項目について
- name
- 作成するアプリケーション名を指定
- dockerComposeFile
- dockerコンテナの作成に使用するdocker-composeファイルを指定
- workspaceFolder
- ワークスペースの展開先ディレクトリを指定
- service
- vscodeで接続するserviceの指定
- extensions
- 開発時に利用するvscodeのextensionを指定
- settings
- vscodeの格設定を指定
- shutdownAction
- vscode終了時のコンテナに対するアクションを記述
noneの場合、vscodeが終了後もコンテナが起動したままとなります。起動確認
- vscodeを開く
- command + shift + P
- >Remote-Containers: Rebuild and Reopen in Container
- 正常に立ち上がれば成功
link
公式ドキュメント
docker-compose
ASP.NET Core向けのDocker イメージ
elk-docker
Developing inside a Container using Visual Studio Code Remote Development参考にさせていただいた記事
【2020年1月】令和だし本格的にVSCodeのRemote Containerで、爆速の"開発コンテナ"始めよう
DockerでMySQLを使ってみる
初めてのElasticsearch with Docker次回
アプリケーションコードについて -> .NET Core + MySQL + Elasticsearch(Nest)構成でクリーンアーキテクチャなバックエンド開発
- 投稿日:2020-10-25T01:55:29+09:00
Spring Boot と Cloud Sql を接続
どんな記事?
???「時代はもうクラウドなんですよ!!」
僕もそう思うのでクラウドを使ってみようと思います。Google信者なので GCP を使います。
とりあえずローカル環境からDBに接続してデータのやり取りをします。使うもの
1.GCP のアカウント(事前に取得しておく)
2.GCP のDBインスタンス(MySQL で作ってます。チュートリアル見ながら事前に作成)
3.Spring Boot
※1 Spring JPA を依存に含めています。それ以外は他のSpringInitializer記事を参考に。
※2 個人的な趣味でMavenを使います。目標
クラウド上のDBと接続して、データのやりとりができるようになる。
ステップ1:アプリを作る
データのやり取りをするだけでよいので、api として実装します。
DataGetController.java@RestController public class DataGetController { @Autowired TestDataService testDataService; @RequestMapping(value = "/api/data/get", method = RequestMethod.GET) public List<TestData> doGet() throws Exception { return testDataService.getTestDataAll(); } }TestDataService.java@Service public class TestDataService { @Autowired TestDataRepository testDataRepository; public List<TestData> getTestDataAll() { return testDataRepository.findAll(); } @Transactional public boolean insertTestData(final TestData testData) { testDataRepository.save(testData); return true; } }TestData.java@Entity @Table(name = "test_data") public class TestData{ @Id @GeneratedValue private Integer id; @Column(name = "name", nullable = false) private String name; private int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }※Repositoryは割愛します。JPA の記事を見てみてください。
ステップ2:設定する
公式を参考にしながら設定を書いていきます。
App Engine から Cloud Sql への接続公式ではjavaなどのソース上で書いていますが、Spring Boot でやる場合は起動時に設定ファイルで読み込ませてあげないと勝手に違うものを見に行こうとして落ちます。(1敗)
なので設定ファイルで読み込ませてあげる必要があるんですね。application.ymlspring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://XXX.XXX.XXX.XXX/test_database username: root password: root type: com.zaxxer.hikari.HikariDataSource hikari: data-source-properties: socketFactory: com.google.cloud.sql.mysql.SocketFactory cloudSqlInstance: XXXX:asia-northeast1:XXXXそのままymlに書き換えただけです。
ステップ3:依存関係の追加
このまま起動すると落ちます。
見慣れない例外がだらだら出てきます。
これを書いている今は解決済みで、例外名なんかは忘れてしまいましたが……調べてみると色々とやることがあるみたいですが、まずは依存関係の追加です。
Cloud Sql に接続するには以下を追加します。
pom.xml<!-- project配下 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-gcp-dependencies</artifactId> <version>1.2.3.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.google.cloud</groupId> <artifactId>libraries-bom</artifactId> <version>11.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ~~~ <!-- dependencies配下 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId> </dependency> <dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-storage</artifactId> </dependency>これでOKです。
ステップ4:設定の追加
まだ落ちます。
Cloud Sql と接続するのに必要な設定を yml に追加します。application.yml#以下を追加 cloud: gcp: sql: database-name: test_database instance-connection-name: XXXX:asia-northeast1:XXXXこれさっき書いてなかった? って思った方もいらっしゃるかと思いますが、さっきも書きました。
ですが、プロパティ名が違いますね。
さっきのはHikariCP というライブラリでコネクションプールを作るためのものでした。
今度はCloud Sql と接続するための設定です。ステップ5:実行
多分ここまでで動くと思います。
localhost:8080/api/data/get [{"name":"name1"}]事前に入れておいたデータが返ってきました。
まとめ
これでクラウドエンジニアの仲間入りです!!
仲間に入れてください!読んでいただき、ありがとうございました。
間違いやご指摘あれば、コメントにお願いしますmm
- 投稿日:2020-10-25T00:14:22+09:00
Laravel で DROP TABLE したテーブルを再び migrate する方法
一度 DROP TABLE したテーブルを再び migrate する
DROP TABLE は表構造ごと完全削除します。そのため,再びテーブルが必要になった場合はもう一度 migrate を実行する必要があります。
しかし,そのままでは migrate できないので以下に対処法を紹介します。
試しに DROP TABLE してみる。
mysql> USE mydb; mysql> DROP TABLE my_table;テーブルが消えた状態で migrate を実行しようとすると,migrate するものがないと言われてしまいます。
> php artisan migrate Nothing to migrate.migrations テーブルから, DROP したテーブルの id を確認します。
mysql> SELECT * FROM migrations;該当するレコードを削除します。
Laravelでは migrations テーブルでマイグレーションの実行履歴を管理しています。
既にテーブルを削除していても,この履歴を消さない限り migrate は実行されません。mysql> DELETE FROM migrations WHERE id='20';履歴を削除したので migrate を実行します。
> php artisan migrate無事にテーブルが作成されました。