buto > /dev/null

だいたい急に挑戦してゴールにたどり着かずに飽きる日々です

not authorized to perform: ses:SendEmailは寝かせて直る!

3日前にAWS Lambdaでメール送信(SES)をしてみるため以下ステップを実行

  1. メール宛先アドレスをSES認証

  2. 公式ガイドをコピペしてSES送信関数を作成

  3. テスト実行

こちらのエラーが発生してメールは送信されませんでした。。

not authorized to perform: ses:SendEmail

Lambda関数にSES送信ロールがないということなのでIAMロールでSES送信を許可してみたが、上記エラーが発生する

今度はIAMロールをjson編集でSES送信を許可したが、変わらず…

画面を更新してリトライ、ロール編集してリトライ…と1時間近く頑張ってみたがダメだった

さっきロール見直ししようかな〜と試しに関数を実行すると、成功してメールが送信された!え、なんで

IAMロールの適用に1時間以上掛かることがある!?とは思えないから、関数実行の画面を開き直す必要があったのかも

ちなみにSES送信できたIAMロールはこちら

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "logs:CreateLogGroup"
            ],
            "Condition": {
                "StringLike": {
                    "ses:FromAddress": "メールアドレス"
                }
            },
            "Resource": [
                "arn:aws:ses:ap-northeast-1:xxxxxxx:identity/メールアドレス",
                "arn:aws:logs:ap-northeast-1: xxxxxxx:*"
            ]
        }
    ]
}

稼働中RDS同士のデータ同期を考える

仕事で既に稼働しているEC2+RDSのセットが2つあってそれぞれのRDSのデータを同期するかも、という話があったので勉強がてら調べてみる

AWS DMS

一般的な方法のように思われる

AWS DMS を活用して MySQLからPostgreSQLへの移行とDBリファクタリングをやっている話/osc2018-kagawa-dms-replication

DMSを使ったSQL Serverの継続的なレプリケーションを行う

データ移行なので基本は1度または何度かの移行タイミングで利用するサービスに思えたけど、

データの更新があったらもう一方のDBへ反映させる継続的レプリケーションという機能があった!

Kafka

打ち合わせの中で出てきた案(そもそも知らない)

データストリーム、データハブ(正確にはメッセージキューというらしい)

オンプレではApacheが提供しているKafkaが使われていてAWSマネージドサービスでも提供されるようになった(Apacheのものより設定が楽らしい)

こちらの記事の図が分かりやすかったです!!

Apache Kafkaの概要とアーキテクチャ

何も考えずにEC2上のアプリ内でデータ抽出+別アプリのDB更新(API実行)をするとデータが大量だった時にEC2の負荷が高くてレスポンス遅くなっちゃう…

公開APIを使ったデータ更新では1度に送信できるリクエストに制限があり、Max件数をリクエストすると非同期で更新処理が実行されるので更新結果取得をポーリング実行してデータが正常に更新できたかを確認する方法を使ったことがあります

ちなみにMax件数をAPIリクエストして更新次第、次のMax件数をリクエスト…を何度か続けて実行したらサーバエラーになって一時的にAPIリクエストが拒否されてしまったこともTT

こんな問題を解消するのがデータハブなんですね!

Kinesis

とてもうっすらとした記憶でキネシスストリームっていうのも同じようなサービスじゃなかったっけ?と浮かんできました

Amazon Kinesis vs. Kafka: データストリームサービス徹底比較

KafkaとAWS Kinesisの比較

Kafkaの方がKinesisと比べるとより大量のデータを瞬時に扱える、でも熟練者じゃないとセットアップが難しいっていう感じなんですね〜(Kinesisがいいじゃん笑)

まとめ

Kafka、Kinesisだったらデータ自体は高速で繋いでくれるけど、DB更新するにはAWS Lambdaでデータが送られてきたことを検知してRDSに登録する必要がありそう

高頻度かつ大量のデータの同期であればKafkaまたはKinesis+AWS Lambdaなんだろうけど、AWS DMSの継続レプリケーションで十分なんじゃないかな

Step Functions勉強会予習

明日AWS Step Functionsの勉強会に参加するのでどんなサービスかだけでも予習しておく!!

分散アプリケーション、マイクロサービスのコンポーネント疎結合化を可能にするAWSのマネージドサービス。

dev.classmethod.jp

分散アプリケーションはブロックチェーンのことですね!

ブロックチェーンって仮想通貨のイメージしかなかったけどゲームとかでもよく使われてるのかあ

www.cryptokitties.co

ネコ育てるか…^--^

あと、マイクロサービスはサービス自体はシンプルにして(領域を絞って)APIでサービス同士を連携して色々な機能を提供できるという構成のサービスですかね

例えばAPIGateway+Lambdaでスケジュール管理、病院での診察内容管理、お薬管理、保険料請求管理、リマインダーと別々の機能(サービス)があり、それらの機能をStepFunctionsで繋げるとペット通院サービスとなる訳ですね!!

私はネコを飼ってますが病院の明細原本は保険料請求のために郵送してしまうので、うちの子のお薬手帳的な物がないんですよね〜

そのうち試してみよ〜

勉強会ではCodeBuildなどを使ったCI/DIでの使い方を教えてくれるらしい!

Python 音声ファイルの周波数を取得する

前回はUSBマイクで録音した音声をwavファイルに保存したので今回はwavファイルの音声から周波数を取得します

こちらの記事をコピペ

Pythonで、Wave音源の周波数と音量(デシベル)を取得する方法[python音声解析 フーリエ変換 FFT]

import sys
import wave 
import numpy as np
import math

# wavファイルを読み込む
FLAME_SIZE  = 100
wf        = wave.open('./voice.wav', 'rb')
frames   = wf.getnframes()   # フレーム数(サンプリング周波数*録音時間)
framerate = wf.getframerate()   # サンプリング周波数(今回は44.2kHz)

wf.setpos(int(frames/2))   # 全フレームの真ん中に位置を移動
buf = wf.readframes(FLAME_SIZE)  # セットした位置からFLAME_SIZEフレームを取得
wf.close()

# 周波数を取得
data = np.frombuffer(buf, dtype= "int16") / 32768.0
spectrum = np.fft.fft(data)
maxvalue = 0
maxidx = 0
tmpvalue = 0
flist = np.fft.fftfreq(FLAME_SIZE, d=1.0/44200)

for j in range( int( len(spectrum) / 2 ) ):
    tmpvalue = spectrum[j]
    if tmpvalue > maxvalue:
        maxvalue = tmpvalue
        maxidx = j

print( str(int(flist[maxidx])) + "Hz" )

FRAME_SIZE は周波数を取得する音声データの範囲だと思うのですが、今回は442HzのAをハミングした音声を読み込んだのでどの範囲でも同じはず!

実行すると以下のように周波数が表示されます!こちらでチューニングして録音したのでピッタリですね!!

f:id:butorisa:20211208225125p:plain

チューニングなしで録音したら1,768Hz(3オクターヴ上!?)と表示されてそんなテクニックはないので正確さには欠けるかもですね。。

Python USBマイクから入力した音声をファイルにする

過去の自分がpipenvを使っていたことをようやく思い出したので、やっとpythonコードが書けるようになりました!!

歌った時に音程のチェックができるプログラムを書きたい

カラオケの採点モードみたいなプログラムを作るのが最終目標です!

(raspberrypiに実装して上手く歌えていたらランプ点灯みたいにしたいなあ)

pyaudio、waveインストール

まずは録音するためのpyaudio、音声ファイルを作成するためのwave

brew install pyaudio
pipenv install pyaudio

pipenvインストールで早速引っかかる。

Creating a virtualenv for this project...
~ 中略 ~
Installing pyaudio...
Error:  An error occurred while installing pyaudio!

よし、pipenvはあきらめてpip使おう。

buto@LisanoMacBook-Air ~ % pip install pyaudio
Collecting pyaudio
  Downloading PyAudio-0.2.11.tar.gz (37 kB)
Could not build wheels for pyaudio, since package 'wheel' is not installed.
Installing collected packages: pyaudio
    Running setup.py install for pyaudio ... done
Successfully installed pyaudio-0.2.11
WARNING: You are using pip version 20.1; however, version 21.3.1 is available.
You should consider upgrading via the '/Users/buto/.pyenv/versions/3.8.2/bin/python3.8 -m pip install --upgrade pip' command.

warning出てるけどまあいっか笑 waveも同様にpipでインストールしました

マイクの設定

私は600円ほどのUSBマイクを買いました(Amazonでは売り切れている人気商品!)

共立プロダクツ MI-305 [USBマイク]

今日はraspberrypiではなくMacBookで試してみるのでマイクをPC内蔵からUSBに切り替えます

f:id:butorisa:20211207232835p:plain

マイクで録音してwavファイルを作成する

Python3で録音してwavファイルに書き出すプログラム

もうコピペでいいですね笑 丁寧なコメントまで書かれているので勉強にもなります

私は録音の秒数を3秒に、サンプルレートを4420Hz、ファイル名をvoice.wavに変更しました

import pyaudio
import wave
 
RECORD_SECONDS = 3 #録音する時間の長さ(秒)
WAVE_OUTPUT_FILENAME = "voice.wav" #音声を保存するファイル名
iDeviceIndex = 0 #録音デバイスのインデックス番号
 
#基本情報の設定
FORMAT = pyaudio.paInt16 #音声のフォーマット
CHANNELS = 1             #モノラル
RATE = 4420              #サンプルレート
CHUNK = 2**11            #データ点数
audio = pyaudio.PyAudio() #pyaudio.PyAudio()
 
stream = audio.open(format=FORMAT, channels=CHANNELS,
        rate=RATE, input=True,
        input_device_index = iDeviceIndex, #録音デバイスのインデックス番号
        frames_per_buffer=CHUNK)
 
#--------------録音開始---------------
 
print ("recording...")
frames = []
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    data = stream.read(CHUNK)
    frames.append(data)
 
 
print ("finished recording")
 
#--------------録音終了---------------
 
stream.stop_stream()
stream.close()
audio.terminate()
 
waveFile = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
waveFile.setnchannels(CHANNELS)
waveFile.setsampwidth(audio.get_sample_size(FORMAT))
waveFile.setframerate(RATE)
waveFile.writeframes(b''.join(frames))
waveFile.close()

このコードを実行すると初回は以下のダイアログが出るのでマイクへのアクセスを許可します

f:id:butorisa:20211208001215p:plain

recording...と標準出力に表示されてから3秒録音され、pyファイルと同じディレクトリにvoice.wavが作成されました!

f:id:butorisa:20211208001521p:plain

ちなみにCHANNELS=2にするとステレオモードになるそうです 私はマイク1個なのでモノラルでいいですね

今さら聞けないステレオとモノラルの違い

余談ですがpyaudioで録音するサンプルコードでは44.1kHzが多かったので調べてみるとCDで使われている周波数だそうです

サンプリング周波数選択のポイント

442Hzでチューニングして歌うと10倍以上の4420Hz以上が録音に適したサンプリング周波数となるのかな??

pipを実行するとNo such file or directoryとなる

久しぶりにpython書こう!ということでライブラリを追加するために pip install ライブラリ を実行したら

/Users/buto/.pyenv/shims/pip: line 21: /usr/local/Cellar/pyenv/1.2.18/libexec/pyenv: No such file or directory

python書き始めることすらできない笑

pyenvをアップグレードして解決!

まずは brew update をしましょう!

Error: 
  homebrew-core is a shallow clone.
To `brew update`, first run:
  git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow

homebrewも使えなくなってる笑 こちらは↑のgit〜を実行して解消されました

その後 brew install pyenv でpyenvをアップグレードしました

~/.bash_profileでpyenvにパスを通してあるか確認(Macに元々インストールされているpythonではなくpyenvのpythonがロードされる設定)

export PATH="$HOME/.pyenv/shims:$PATH"

source ~/.bash_profile してpipすると動いた!!(それでもダメな時はターミナル開き直そう)

Usage:   
  pip <command> [options]

って、あれ?私は確かpyenv+pipenvを使っていたような?ライブラリ管理はpipenvに任せていたんじゃ??

pipenv install ライブラリ でインストールしておけばよかったorz

※ pipはpythonに搭載されているライブラリ管理ですが、pipenvのコマンドと勘違いして無駄にpipenvのアップグレードとかしてしまった笑

DDD(ドメイン駆動設計)を調べてみた

DDD(ドメイン駆動設計)とは

ドメイン(ソフトウェアで問題解決しようとする対象)に焦点を当てるソフトウェアの設計方法

「ドメイン駆動設計」は新人SEの必修項目でいいと思う

コーディングする時サービスクラスに何でもかんでもロジックを詰め込むのではなく、DTOクラスに対象のロジックを書く!って理解(ちゃんと勉強すると実装手法はたくさんありそう)

DDDのモデリングとは何なのか、 そしてどうコードに落とすのか

DTOクラスがgetter/setterしかない状態をドメインモデル貧血症と言うらしい(いつも私が書くコードだ笑)

ドメインモデル貧血症

とにかくやってみる

引数に食材を渡して食材を使う料理を返す処理を作ります

メインメソッドを持つコントローラー

package controller;

import domain.Receipt;
import logic.ReceiptLogic;

public class ReceiptController {

    public static void main(String... args) {

        Receipt receipt = new Receipt(args[0].toUpperCase());
        ReceiptLogic logic = new ReceiptLogic();

        System.out.println(logic.getDate() + "のメニュー : " + receipt.search(receipt.getIngredient()));
    }
}

ドメインクラス 料理の検索メソッドをこちらに書きました!

package domain;

public class Receipt {

    public Receipt(String ingredient){
        this.ingredient = Receipt.Ingredients.valueOf(ingredient.toUpperCase());
    }

    private Ingredients ingredient;

    public Ingredients getIngredient() {
        return ingredient;
    }

    public Dishes search(Ingredients ingredient){
        // 本来はDB接続して料理を検索
        Dishes dish = switch(ingredient){
            case CABBAGE -> Dishes.COLESLAW;
            case EGG -> Dishes.QUICHE;
            case BACON, POTATO -> Dishes.GRATIN;
            case CARROT, ONION, TURNIP -> Dishes.POTAUFEU;
        };
        return dish;
    }
    // 本来はDBに食材や料理を登録しておく
    public enum Ingredients {
        BACON,
        CABBAGE,
        CARROT,
        EGG,
        ONION,
        POTATO,
        TURNIP;
    }

    public enum Dishes {
        POTAUFEU,
        COLESLAW,
        QUICHE,
        GRATIN
    }
}

サービスクラス ドメインとは関係のない日付取得はこちらに書いています

package logic;

import java.time.LocalDate;

public class ReceiptLogic {

    public String getDate(){
        LocalDate now = LocalDate.now();
        return now.getMonthValue() + "/" + now.getDayOfMonth();
    }
}

メインメソッドに引数「cabbage」を渡して実行すると以下が返ってきます

12/4のメニュー : COLESLAW

ドメインクラスに書いたsearchメソッドは今までだったらサービスクラスに書いていました

いつもサービスクラスのコードが多くなってしまい、メンテ時にサービスクラス内で迷子になることがしばしば…

ドメインクラスに対象の処理も書くようにすれば処理詳細を見る前に「何に関わる処理か」というところまでは分かりそう!