[Laravel 8.45.1] マルチテナントアーキテクチャ

Laravelでのマルチテナントは、tenancy/tenancyを使用する。
tenancy/tenancyと、ベースの機能のみで必要な分は後から追加するtenancy/frameworkがある。
tenancy/framework推奨

$ composer require tenancy/framework
$ php artisan make:model Organization -m

create_organizations_table.php

public function up()
    {
        Schema::create('organizations', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();
            $table->timestamps();
        });
    }

$ php artisan migrate

Organization.php

use Illuminate\Http\Request;
use Tenancy\Identification\Contracts\Tenant;

class Organization extends Model implements Tenant
{
    use HasFactory;

    protected $fillable = [
    	'name',
    	'subdomain',
    ];

    protected $dispatchesEvents = [
    	'created' => \Tenancy\Tenant\Events\Created::class,
    	'updated' => \Tenancy\Tenant\Events\Updated::class,
    	'deleted' => \Tenancy\Tenant\Events\Deleted::class,
    ];

    public function getTenantKeyName(): string {
    	return 'name';
    }

    public function getTenantKey(){
    	return $this->name;
    }

    public function getTenantIdentifier(): string {
    	return get_class($this);
    }
}

Tenancy\Identification\Concerns\AllowsTenantIdentification を使うだけでもできる
-テナントが追加されたら、テナント用のデータベースを作成してマイグレーション
-テナントが更新されたらマイグレーション実行
-テナントが削除されたらデータベース削除
-テナントを識別するため、Resolverに登録

app/Providers/AppServiceProvider

use Tenancy\Identification\Contracts\ResolvesTenants;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        //
        $this->app->resolving(ResolvesTenants::class, function(ResolvesTenants $resolver){
            $resolver->addModel(Organization::class);
            return resolver;
        });
    }
}

MySQLのデータベースドライバをインストール
$ composer require tenancy/db-driver-mysql

affects connections : テナントのDBにテナントのDBにアクセスできるようにする
$ composer require tenancy/affects-connections

tenancyの拡張パッケージは Event/Listner で構成
$ php artisan make:listener ResolveTenantConnection
/listeners/ResolveTenantConnection.php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Tenancy\Identification\Contracts\Tenant;
use Tenancy\Affects\Connections\Contracts\ProvidesConfiguration;

class ResolveTenantConnection
{
    public function handle(Resolving $event)
    {
        //
        return $this;
    }

    public function configure(Tenant $tenant): array {
        $config = [];

        event(new Configuring($tenant, $config, $this));

        return $config;
    }
}

/app/Providers/EventServiceProvider.php

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [

        \Tenancy\Affects\Connections\Events\Resolving::class => [
            App\Listeners\ResolveTenantConnection\ResolveTenantConnection::class,
        ],
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];
}

$ composer require tenancy/hooks-migration
$ php artisan make:listener ConfigureTenantMigrations

use Tenancy\Hooks\Migration\Events\ConfigureMigrations;
use Tenancy\Tenant\Events\Deleted;

class ConfigureTenantMigrations
{
    public function handle(ConfigureMigrations $event)
    {
        //
        if($event->event->tenant){
            if($event->event instanceof Deleted){
                $event->disable();
            } else {
                $event->path(database_path('tenant/migrations'));
            }
        }
    }
}

EventServiceProvider.php

use Illuminate\Support\Facades\Event;
use App\Listeners\ConfigureTenantMigrations;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
        $this->app->resolving(ResolvesTenants::class, function(ResolvesTenants $resolver){
            $resolver->addModel(Organization::class);
            return resolver;
        });
    }

    protected $listen = [
        \Tenancy\Hooks\Migration\Events\ConfigureMigrations::class => [
            ConfigureTenantMigrations::class,
        ],
    ];
}

$ php artisan make:listener ConfigureTenantSeeds

use Database\Tenant\Seeders\DatabaseSeeder;
use Tenancy\Hooks\Migration\Events\ConfigureSeeds;
use Tenancy\Tenant\Events\Deleted;
use Tenancy\Tenant\Events\Updated;

class ConfigureTenantSeeds
{
    public function handle(ConfigureSeeds $event)
    {
        //
        if($event->event->tenant){
            if($event->event instanceof Deleted || $event->event instanceof Updated){
                $event->disable();
            } else {
                $event->seed(DatabaseSeeder::class);
            }
        }
    }
}

EventServiceProvider.php

    protected $listen = [

        \Tenancy\Affects\Connections\Events\Resolving::class => [
            App\Listeners\ResolveTenantConnection\ResolveTenantConnection::class,
        ],
        \Tenancy\Hooks\Migration\Events\ConfigureMigrations::class => [
            ConfigureTenantMigrations::class,
        ],
        \Tenancy\Hooks\Migration\Events\ConfigureSeeds::class => [
            ConfigureTenantSeed::class
        ],
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];

.env.testing

TENANT_SLUG=test

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=test_central
DB_USERNAME=root
DB_PASSWORD=password

あれ? これでどうやって実装するのかようわからんな。。

エンジニアがベンチャーキャピタルと面談(資金調達)する時のコツ

エンジニアであれば、プロダクトを開発してベンチャーキャピタルに資金調達を依頼する機会が何度かあるであろう。
今日私はVCと面談した。その経験を元に、大事なことと感じた内容を整理したい。

■1.事業の拡大
これが全てだと断言して良い。ベンチャーキャピタルが興味を持っているのは「どうやって事業を拡大するか」の一点に尽きる。もちろんIPOを見据えての事だが、Buyoutでも数十億以上で売れないと意味がないらしい。
つまり、スケールしなければ、全く興味がなく、「equityで調達する意味ない」と言われる。
よって、どうやって事業を拡大するかを説得できるように準備して面談に望む事。

■2.課題・悩み事
どういうことに悩んでいるか、面談で言ってみると良い。それに対して、ベンチャーキャピタリストの見解を答えてくれる。

■3.技術的なアドバンテッジについて
VCはコンサルに近い。相手によるかと思うが、どういう技術を使っているかなどは、聞かれたら答える程度で良い。
企画や営業と話す時のように、噛み砕いで話す姿勢を見せた方が良いように思った。
それよりもビジネスとして競合よりもどう優れているかを説明しなければいけない。

■4.質問の準備
10個くらい質問を準備して面談に臨んだが、全く意味がなかった。
就活の面談ではないので、「相手に興味を示す」というのは全然求められていないようだった。
(投資で重視されていること、サポート体制、何故VCをやっているのかなど色々聞こうと思っていたが、それはナンセンスな質問だと悟った)

■5.実績のアピールが重要
サービスに関する実績をアピールすることが大事。
「どういう実績があるからこのサービスを作っている」ということを上手く説明するのが大事だ。
VCは、何故そのサービスをやっているかについては興味を持っている。

ということで、私は面談は撃沈したのだが、参考にして欲しい。
ベンチャーキャピタルの面談では、開発プロジェクトの面談などと違って、ゼロから1まで全て開発したとか、どの機能の開発に工夫しているなんてことは一切聞かれない。

——-
追加情報1
■6. ストーリーが大事
営業戦略、売上をどう立てていくか

■7. 売上10億立てないと駄目
上場基準として10億円ないと上場できない

■8. バリエーション
リード投資もしくは事業会社にバリエーションを作ってもらう

——-
追加情報2
■9. 組織体制
強いエンジニア、営業スタッフを揃えているか重視している

■10. ユーザヒヤリング
ニーズをきちんと聞くこと
ヒヤリングの仕組み作りを検討する

■11. 組織的な強みを作ること
競合にはない強みを持つこと

■12. 事業計画の立て方
事業計画の作成能力は大事
初年度などは赤字の方が見栄えが良い
売上が取れないことが最も多く致命的な問題

■13. リプレイス
システムのリプレイスの売り方を考える

[Python3 音声認識] juliasを使ってみる

### juliusとは
– 京大や名古屋工業大が開発しているオープンソース音声認識ライブラリ
– C言語で書かれている
– 独自の辞書モデルを定義することが可能
– 単体では動作せず、言語認識をするモデルを読み込んで動かす
– Juliux GitHub: https://github.com/julius-speech/julius

### julius install
$ sudo yum install build-essential zlib1g-dev libsdl2-dev libasound2-dev
$ git clone https://github.com/julius-speech/julius.git
$ cd julius
$ ./configure –enable-words-int
$ make -j4
$ ls -l julius/julius
-rwxrwxr-x 1 vagrant vagrant 700208 Jul 31 16:39 julius/julius

### julius model
https://sourceforge.net/projects/juliusmodels/
「ENVR-v5.4.Dnn.Bin.zip」をDownloadします。
$ unzip ENVR-v5.4.Dnn.Bin.zip
$ cd ENVR-v5.4.Dnn.Bin
$ ls
ENVR-v5.3.am ENVR-v5.3.layer5_weight.npy ENVR-v5.3.prior
ENVR-v5.3.dct ENVR-v5.3.layer6_bias.npy README.md
ENVR-v5.3.layer2_bias.npy ENVR-v5.3.layer6_weight.npy dnn.jconf
ENVR-v5.3.layer2_weight.npy ENVR-v5.3.layerout_bias.npy julius-dnn-output.txt
ENVR-v5.3.layer3_bias.npy ENVR-v5.3.layerout_weight.npy julius-dnn.exe
ENVR-v5.3.layer3_weight.npy ENVR-v5.3.lm julius.jconf
ENVR-v5.3.layer4_bias.npy ENVR-v5.3.mfc mozilla.wav
ENVR-v5.3.layer4_weight.npy ENVR-v5.3.norm test.dbl
ENVR-v5.3.layer5_bias.npy ENVR-v5.3.phn wav_config

$ sudo vi dnn.jconf

feature_options -htkconf wav_config -cvn -cmnload ENVR-v5.3.norm -cvnstatic
// 省略
state_prior_log10nize false // 追加

### Recognize Audio File
$ ../julius/julius/julius -C julius.jconf -dnnconf dnn.jconf
————-pass1_best: the shower
pass1_best_wordseq: the shower
pass1_best_phonemeseq: sil | dh iy | sh aw ax r
pass1_best_score: 130.578949
### Recognition: 2nd pass (RL heuristic best-first)
STAT: 00 _default: 21497 generated, 4114 pushed, 332 nodes popped in 109
ALIGN: === word alignment begin ===
sentence1: the shower
wseq1: the shower
phseq1: sil | dh iy | sh aw ax r | sil
cmscore1: 0.552 0.698 0.453 1.000
score1: 88.964218
=== begin forced alignment ===
— word alignment —
id: from to n_score unit
—————————————-
[ 0 11] 0.558529 []
[ 12 30] 2.023606 the [the]
[ 31 106] 2.249496 shower [shower]
[ 107 108] -2.753532
[
]
re-computed AM score: 207.983871
=== end forced alignment ===

——
### read waveform input
1 files processed

音声認識をやってるっぽいのはわかるけど、Google-cloud-speechとは大分違うな

$ sudo vi mic.jconf

-input mic
-htkconf wav_config
-h ENVR-v5.3.am
-hlist ENVR-v5.3.phn
-d ENVR-v5.3.lm
-v ENVR-v5.3.dct
-b 4000
-lmp 12 -6
-lmp2 12 -6
-fallback1pass
-multipath
-iwsp
-iwcd1 max
-spmodel sp
-no_ccd
-sepnum 150
-b2 360
-n 40
-s 2000
-m 8000
-lookuprange 5
-sb 80
-forcedict

$ ../julius/julius/julius -C mic.jconf -dnnconf dnn.jconf
Notice for feature extraction (01),
*************************************************************
* Cepstral mean and variance norm. for real-time decoding: *
* initial mean loaded from file, updating per utterance. *
* static variance loaded from file, apply it constantly. *
* NOTICE: The first input may not be recognized, since *
* cepstral mean is unstable on startup. *
*************************************************************
Notice for energy computation (01),
*************************************************************
* NOTICE: Energy normalization is activated on live input: *
* maximum energy of LAST INPUT will be used for it. *
* So, the first input will not be recognized. *
*************************************************************

——
### read waveform input
Stat: adin_oss: device name = /dev/dsp (application default)
Error: adin_oss: failed to open /dev/dsp
failed to begin input stream

使い方がイマイチよくわからん
全部コマンドラインでアウトプットが出てくるんかいな