20200908のSwiftに関する記事は10件です。

TestFlight「"app name"をインストールできません あとでやり直してください」が起きて原因がキレそうだった件

はじめに

タイトル通り、TestFlightにて急に昨日まで問題なかったインストールができなくなり、原因が謎過ぎて、今日7時間ぐらい使ってしまったのでキレそうになり、そのノリで書いてます。

原因

接続しているノード(WiFi)が問題でした、、
自宅のものでは問題なかったのですが、良くあるセキュアなWiFiではTestFlightからのインストールが出来ませんでした。
4Gに変えた瞬間解決しました(キレそう)

ただ、恐らくWiFiが問題と言うより、MacBook Proの方で先にWiFiにつないで認証した後だと、同じapple idを使っているiPhoneでは認証を求められなかった為だと思われます。

原因が分からなすぎて、冗談抜きで証明書50回ぐらい作り直して、試したので証明書周りはもう脳死で出来る様になりました?

後、試せる人が他にいればその人に試してもらうのが一番早いです!
その人が問題無ければ環境に違いって事に目付けれるので

owari

何が言いたいかって、こんなものに時間を割いてるは本当にもったいないです、非合理的過ぎます。
と言う事でこう言うヤバ目の記事はなかったので誰かの為に置いておきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

設計パターンMVCを学んだ

設計について

まだまだ初学者なので、いろいろと学習の基礎が疎かです。特に今まで設計の学習全くしてこなかったので、MENTAで課題をだしていただき、ハンズオンで取り組みました。そこで学んだことを簡単にメモ書きします。
なお、学習したのはMVCのみです。

MVC: Modelにビジネスロジックを書く

MVCという設計について学習しました。
MVCそれぞれの頭文字は以下の略称です。
M:Model
V:View
C:Controller

今まで参考にしてきた学習教材が良くなかったのか、Modelの役割をちゃんと理解していませんでした。Modelには主にビジネスロジックを記述すると言われています。ビジネスロジックとは簡単に言えば、アプリやシステムで必要不可欠な処理(例えば、Firebaseのログインの処理DBからの読み込み書き込みAPI通信処理など)を指しています。

参考

Viewは見た目の規定であり、ControllerはViewとModelの橋渡し的な役割です。iOS開発ではViewとControllerが分かれているか、いないかが重要なポイントだそうです。分かれない場合、ViewControllerのようなフォルダ構成をとったりします。今回はViewControllerのパターンで学習しました。本来、わかれている方が良いとされてるみたいです。

加えて、ModelからViewControllerへの橋渡しをプロトコルで行うのかKVOで行うのかが重要だそうです。今回はプロトコルのパターンを使って学習しました。

以下、コード例

protcolとDelegate
// Model
protocol SampleModelDelegate: AnyObject {
    func sampleAction(with someData: Data)
}

final class SampleModel {
    // Model で delegate の通知先を弱参照で保持
    weak var delegate: SampleModelDelegate?
    func sampleAction() {
        let urlString = "https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        let url = URL(string: urlString)
        AF.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default).responseJSON { response in
            switch response.result {
                case .success:
                    // JSON からの変換 = デコード
                    do {
                        guard let data = response.data else { return }
                        let items = try JSONDecoder().decode([Items].self, from: data)
                        print(items)
                        self.delegate?.getQiitaData(items: items)
                    } catch {
                        // デコードのエラー
                        print(error)
                    }
                case .failure:
                  print("failure")
            }
        }
    }
}



// ViewController
final class SampleViewController: UIViewController {
    private let model = SampleModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        // model の delegate の通知先に自身を設定
        model.delegate = self
    }
}

※なおKVOとはKey Value Observeの略みたいです。
実装したことがないのでまだよくわかりません。

要するにRuby on RailsのMVCとほぼ同じ?

iOS開発では、ViewとControllerが分かれない場合、ViewControllerのようにすると記述しました。これ以外を除けば、Modelの役割ってRailsチュートリアルで学んだModelとほぼ同じでは!?と学習途中で気付きました。泣

これから

MVCは現場ではもう使われないなどの記事を読みましたが、とりあえずはMVCをきっちり理解して使えるようになってから異なる設計パターンを学習していきたいと思います。MVVMMVPクリーンアーキテクチャVIPER(バイパー)などなど。
学んだら上記にまとめていこうと思います。

備考: こちらのGitHubのソースMVCの良い例になるらしいので読んでおきたい。
また、KickstarterのOSSはMVVMの学習時に良いらしい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TextFieldの内部に×ボタンを配置する

完成系

スクリーンショット 2020-09-08 18.54.37.png

やり方

TextFieldにはrightViewというプロパティがあるのでそこにボタンをセットしてあげます。
ただ、rightViewに直接ボタンを配置すると大きさを指定しても拡大されてしまうので、あいだにUIViewを挟みます。

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        let view = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        //ボタンのY座標は(TextFieldの高さ-ボタンの高さ)/2
        let button = UIButton(frame: CGRect(x: 5, y: 5, width: 20, height: 20))
        button.contentEdgeInsets = .init(top: 0, left: 0, bottom: 3, right: 0)
        button.setTitle("×", for: .normal)
        button.backgroundColor = .lightGray
        button.layer.cornerRadius = button.frame.width / 2
        //ボタンに処理を追加
        button.addTarget(self, action: #selector(tappedButton), for: .touchUpInside)
        view.addSubview(button)
        //デフォルトだと表示されていない
        textField.rightViewMode = .always
        textField.rightView = view
    }

    //タップされたときの処理
    @objc func tappedButton() {
        print("tapped")
    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者向け]DKimagePickerControllerのススメ

みなさんImagePickerController使ってますかー??
ライブラリから写真選択出来るヤツですね!!

でもImagePickerって写真一枚しか選択できないんですよね、、
オリジナルアプリ作りたかった僕は、なんとか複数選択出来るの探しました!

8efce1b680fc1855f6785e8ba7b34374.gif

はい、これです!!
僕は実際に触らないとわかんないのでこちらを参考に作ってみました。
https://github.com/metasmile/DKImagePickerController
https://teratail.com/questions/189732

スクリーンショット 2020-09-08 18.15.12.png
ビルドして改めてコードみてみるとふむふむ、、
アセッツに入れてcollectionViewで表示するのか!

外部のライブラリを使うのは初めてでメンターに聞きまくってわかりました!

fetchFullScreenImage この中でループ回すのか、、、
DKImagePickerController これをインポートするのか、、
KDAssetsってのがあるのか、、

分かったようでわかりませんでした:joy_cat:
Swift深い!!!

僕が作ったサンプルもどうぞ!
https://github.com/rentamaeda/DKImagePickerTest

参考にしたサイト等

https://github.com/metasmile/DKImagePickerController
https://teratail.com/questions/189732

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RxSwift で非同期処理を合成しよう

今回は同じ型の非同期処理を RxSwift でまとめる際に使用する concatmerge の使い方と挙動を簡単にまとめとこうと思います。

concat

image.png

concat() は複数の非同期処理を渡された順番で順次処理を行っていきます。サンプルは次のようになります。

    let ob1 = Observable<String>.create { observer -> Disposable in
        // 3秒後にイベントを流す
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            observer.onNext("ob1")
            observer.onCompleted()
        }
        return Disposables.create()
    }
    let ob2 = Observable<String>.create { observer -> Disposable in
        // 2秒後にイベントを流す
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            observer.onNext("ob2")
            observer.onCompleted()
        }
        return Disposables.create()
    }

    Observable.of(ob1, ob2)
        .concat()
        .subscribe(onNext: { str in
            print(str)
        })
        .disposed(by: disposeBag)

    // 出力:
    //
    // ob1
    // ob2
    //

また、Observable の合成は下記のように書くこともできます。

    Observable.concat(ob1, ob2)

merge

image.png

merge() は複数の非同期処理を並列に実行することができます。つまり、Observable の渡される順番などが関係なく処理が早く終わった順にストリームに流れます。下記がサンプルコードになります。

    let ob1 = Observable<String>.create { observer -> Disposable in
        // 3秒後にイベントを流す
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            observer.onNext("ob1")
            observer.onCompleted()
        }
        return Disposables.create()
    }
    let ob2 = Observable<String>.create { observer -> Disposable in
        // 2秒後にイベントを流す
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            observer.onNext("ob2")
            observer.onCompleted()
        }
        return Disposables.create()
    }

    Observable.of(ob1, ob2)
        .merge()
        .subscribe(onNext: { str in
            print(str)
        })
        .disposed(by: disposeBag)

    // 出力:
    //
    // ob2
    // ob1
    //

じゃあ異なる型の Observable はどうなるの?

基本的には、ストリームのイベントを逐次検知する必要がある場合は Observable の型を統一して、concat なり merge なりを使用する必要があります。並列で処理を実行して全ての処理が完了したタイミングで値を参照する場合は、zip という関数が用意されていますが、直列で実行が完了した値を参照したい場合は flatMap なり、concat なりを使って実装する感じでしょうか?(こんな方法があるよってやつがあれば教えてください?)

直列で逐次イベントを検知

    enum Container {
        case string(String)
        case int(Int)
    }
    let ob1 = Observable<String>.create { observer -> Disposable in
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            observer.onNext("ob1")
            observer.onCompleted()
        }
        return Disposables.create()
    }.map { Container.string($0) }

    let ob2 = Observable<Int>.create { observer -> Disposable in
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            observer.onNext(2)
            observer.onCompleted()
        }
        return Disposables.create()
    }.map { Container.int($0) }

    Observable.of(ob1, ob2)
        .concat()
        .subscribe(onNext: { c in
            switch c {
            case .string(let str):
                print("string: \(str)")
            case .int(let num):
                print("int: \(num)")
            }
        })
        .disposed(by: disposeBag)


    // 出力:
    //
    // string: ob1
    // int: 2
    //

並列で逐次イベントを検知

    enum Container {
        case string(String)
        case int(Int)
    }
    let ob1 = Observable<String>.create { observer -> Disposable in
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            observer.onNext("ob1")
            observer.onCompleted()
        }
        return Disposables.create()
    }.map { Container.string($0) }

    let ob2 = Observable<Int>.create { observer -> Disposable in
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            observer.onNext(2)
            observer.onCompleted()
        }
        return Disposables.create()
    }.map { Container.int($0) }

    Observable.of(ob1, ob2)
        .merge()
        .subscribe(onNext: { c in
            switch c {
            case .string(let str):
                print("string: \(str)")
            case .int(let num):
                print("int: \(num)")
            }
        })
        .disposed(by: disposeBag)


    // 出力:
    //
    // int: 2
    // string: ob1
    //

並列で完了イベントを検知

zip を使うと上記2つの方法とは違い型を統一する必要がないので、よりシンプルに実装することができます。

    let ob1 = Observable<String>.create { observer -> Disposable in
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            observer.onNext("ob1")
            observer.onCompleted()
        }
        return Disposables.create()
    }

    let ob2 = Observable<Int>.create { observer -> Disposable in
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            observer.onNext(2)
            observer.onCompleted()
        }
        return Disposables.create()
    }

    Observable.zip(ob1, ob2)
        .subscribe(onNext: { str, num in
            print("string: \(str), int: \(num)")
        })
        .disposed(by: disposeBag)

    // 出力:
    //
    // string: ob1, int: 2
    //

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】@escapingをつける必要があるのはどんなときか

先日、窓からさしこむ夏の日差しを眺めながら、とあるLoaderを書いていたのですが、

Escaping closure captures non-escaping parameter 'completion'

というエラーが出ました。
コードをPlaygroundで再現するとこんなんです。

import UIKit

struct Translator {
    func translate(word: String) {
        let loader = Loader()
        loader.load {(string) in
            print(string)
        }
    }
}

struct Loader {
    func load(completion: (String) -> Void) {
        let request = URLRequest(url: URL(string: "https://qiita.com/")!)
        let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
            // 本当はサーバーからのレスポンスを詰めているがサンプルコードなのでベタ書きする
            let string = "Hello"
            completion(string) // ここでコンパイルエラー
        }
        task.resume()
    }
}

let translator = Translator()
translator.translate(word: "こんにちは")

対策としては、引数のcompletionに@escapingをつける、以上なんですが、
つけなきゃいけないケースとつけなくてもいいケースがよくわからないなと思って、調べてみました。

@escapingの意味

Swift実践入門の説明を引用すると、

escaping属性は、関数に引数として渡されたクロージャが、関数のスコープ外で保持される可能性があることを示す属性です

という説明になっています。
説明が難しい属性なので、こういう表現になるのかなと思いますが、
@escapingをよく理解していない人間が読んでも、ちょっと抽象的で、わかったようなわからないような……という気持ちになりました。

クロージャーのそもそもの性質をきちんと理解する

@escapingを正確に理解するためには、下記2つのクロージャーの基本的な性質を理解していることが前提になります。

  • クロージャーは自分が定義されたスコープをキャプチャする
  • クロージャーは関数の引数として使える

そして、この2つの性質を満たすとき、

関数の引数としてクロージャーを使って、その関数が終わった後に実行されると……?

を考えます。

関数の引数としてクロージャーを使って、その関数が終わった後に実行される

冒頭で紹介したサンプルコードみたいなケースなんですが、ちょっとごちゃついてるので、もう少しシンプルな例で説明したいので、
公式サンプルを引っ張ってきました。

公式サンプル
var completionHandlers = [() -> Void]()
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

completionHandlersがクロージャーの配列になっており、関数someFunctionWithEscapingClosure(_:)は引数で受け取ったクロージャーをそこにappendしていきます。

実行はこのコードの中だとまだ行われていません。
どこで行われるでしょうか?
配列completionHandlersにアクセスできる場所ならどこでも実行できますね。

何が問題か?

引数にクロージャーがあるからといって、常に@escapingが必要なわけではありません。
むしろ必要なケースの方が少ないでしょう。
たとえば、下記は不要な例です。

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}

クロージャーの実行が関数内のスコープで完結しているので、問題がありません。
では関数の外で実行されると何が問題なのでしょうか?

IMG_4A7F3958B48C-1.jpeg

簡単ながら図示してみました。

問題がないsomeFunctionWithNonescapingClosure()は、Scope1/Scope2で処理が完結しています。
someFunctionWithNonescapingClosure()と引数のclosureは別スコープで定義されていますが、
関数の実行中はクロージャー及びその親スコープの状態が不変であることが保証されています。

ところが、someFunctionWithEscapingClosure()の方は、配列completionHandlersを実行する、別のScope3が存在します。

クロージャーは自分が定義されたスコープをキャプチャするので、Scope1への参照ができる必要があります。
しかし図のケースだと、Scope3で実行されるときに、Scope1が必ず解放されていない保証はありません。

// Scope 2
var completionHandlers = [() -> Void]()
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

// Scope 1
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
    }
}

// Scope 3(この例だとScope 2と同じだけど、@escapingないとコンパイラに怒られる)
let instance = SomeClass()
instance.doSomething()
completionHandlers.first?()
print(instance.x)
// Prints "100"

クロージャーの中の書き方によっては、循環参照を起こす危険性があるので、クロージャーの中で親スコープを参照するときはselfをつけることになります。

クロージャーをescapeさせる必要があるシチュエーションってなんだ?

で、今回たまたま仕事でLoader書いてて@escapingつけなきゃいけないシーンにぶち当たったんですが、
そもそもクロージャーをescapeさせる必要があるシチュエーションって現実的になんでしょう?

swiftでクロージャーに@escapingをつける場合を調べてみたを読むと、

  1. クロージャーがプロパティとして保存される(強参照される)
  2. クロージャーがメソッド内ですぐに実行されない(非同期である)

ということなんですが、実際そんな設計にしますかね?
Swiftの公式サンプルもクロージャーの配列なんてつくってますが、実際そんなの書きたいときありますか?
そもそも最初に書いたサンプルのURLSessionはなぜ怒られたんでしょう?

そんなことを考えていくと、一つの答えにたどりつきました。
処理をキューイングさせたいケースでは、クロージャーを配列で保持するコード設計になるかと思います。
具体的には、下記のようなケース。

  • OperationQueueを使ってマルチスレッドでタスクを実行したい
  • URLSessionを使ってタスクを並列で実行したい
  • タイマーで所定の時間にいくつかのタスクの実行を予約したい

URLSessionもよく見ると、delegateQueueというタスクをキューイングするプロパティを持っていて、
クロージャーでの実行もおそらくこっちに投入されるのではないかと思われます。

https://developer.apple.com/documentation/foundation/urlsession/1411571-delegatequeue

というか3つ例を出しましたけど、つまるところ複数タスクを非同期で並列処理させたいときですね。

まとめ

わかりやすく書きたいと思って書いたのですが、わかりやすいかは微妙です……
何かありましたら、お気軽にコメント・編集リクエストくださいー

環境

Swift 5.2.4。
Swift2以下だと、クロージャーのデフォルトが@escapingだったとか。

参考

https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID546

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIPageViewControllerのUIPageControlのサイズを変更する

UIPageViewControllerのデフォルトでくっついてるUIPageControlのサイズを変更するときのTipsです。

UIPageControl自体のサイズを変更する方法

override open func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    for subV in self.view.subviews {
        if type(of: subV).description() == "UIPageControl" {
            subV.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)// ここに指定する
        }
    }
}

UIPageControlのドットのサイズを変更する方法

override open func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    for subV in self.view.subviews {
        if type(of: subV).description() == "UIPageControl" {
            subV.subviews.forEach {
                $0.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift5でメインスレッドかどうか確認する

SwiftではUIの更新はMain threadで実行しなければならないというルールがあります。
そのため非同期的にMain threadで処理する場合は DispatchQueue.main.async を使用しますが、非同期処理をする必要のない箇所で無闇に使用してしまうと処理フローの制御がしづらくなるなどの問題が起きてしまうため、サブスレッドのタスク内でのUIの更新などに限定する必要があります。

メインスレッドで実行されているかどうか確認する方法

print(Thread.current.isMainThread)

スレッドへの理解が浅いままasyncを使用していてUI更新が重くなり挙動がおかしくなっていたので、上記で確認しつつ不要な場合は同期処理で行うように修正しました。

参考

DispatchQueueによる非同期処理を見直す
Swift4でDispatchQueueを使う

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Alamofire 5.x~のタイムアウト値(timeoutInterval)設定方法

調べてもあまり参考になるサイトが出てこなかったので、備忘録

環境

  • Xcode: 11.3.1
  • Swift5
  • Alamofire 5.2.1

・requestModifierを指定

        AF.request("https://xxxxxxxxxxxx",
                   method: .get,
                   parameters: parameters,
                   requestModifier: { $0.timeoutInterval = 5.0 }).responseJSON { response in
        }

AF.requestに「requestModifier: { $0.timeoutInterval = 5.0 }」を追加することで、
タイムアウト値をセットすることが可能となる。
※上記コードでは5.0秒にセットしています。こちらは任意の秒数に変更して下さい。

備考

アプリ公開しました!よろしければインストールお願いします。
とらんぽ

Twitter始めました!よろしければフォローお願いします。
@yajima_tohshu

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドメイン駆動設計における5層アーキテクチャの全体図とソースの実装例

今回の記事では、過去に記述した以下のバックエンドに関する記事を一つに統合した内容となっている。
従来のドメイン駆動設計4層アーキテクチャにDCIアーキテクチャを組み込んでいく過程で、様々なパラダイムが発生し、
最終的に4層アーキテクチャミッション層を追加した5層アーキテクチャという形に落ち着いた。
今回の記事では、投稿した過去の記事の内容を5層アーキテクチャによって概念を整理し、整合性を担保している。

投稿した過去の記事一覧

・ドメイン駆動設計のユースケース層(アプリケーション層)を軍事思想(作戦術)でドーピング
https://qiita.com/aLtrh3IpQEnXKN7/items/853ecb3cd109dd016476

・OOUI(オブジェクト指向設計)によるバックエンド設計のパラダイムシフト
https://qiita.com/aLtrh3IpQEnXKN7/items/3aa05f9628544c43f279

・DCIアーキテクチャの活用方法を紹介
https://qiita.com/aLtrh3IpQEnXKN7/items/355ad12f82ac424abea3

・エリック・エヴァンスが提唱したアーキテクチャの4層モデルを拡張する
https://qiita.com/aLtrh3IpQEnXKN7/items/b7fe2014ccefcbb9e458

・ドメイン駆動設計のミッション層を設計する手法を紹介
https://qiita.com/aLtrh3IpQEnXKN7/items/98e0c2d2ee0776e0e039

・5層アーキテクチャモデルにおけるドメイン分析
https://qiita.com/aLtrh3IpQEnXKN7/items/e5d1a276c07f1fe140be

各レイヤーの説明

レイヤーの全体図

image.png

1.コントロール層

アプリ外からアクセスの入り口となるインターフェースを主にRestApiで提供する。アプリ外からのアクセスがWeb、iOS、Androidと増加するたびにインターフェースを追加する必要がある。
インターフェース以外の機能として、コントロール層からユースケース層へデータを受け渡す際のデータ変換、コントロール層からアクセス元へのデータ変換(JSON化)を行う。データの受け渡し、データ変換機能をメインに担当する。

2.ユースケース層

ビジネスロジックで実現したい目的を記載するための層。ミッション層のメソッドの呼び出しのみを行う。
ユースケース層では、フロントエンドから取得したデータを格納するためのBeanクラスを必ず用意する。
Beanクラスミッション層へ引数として渡し、ミッション層内で具体的なロジック内容を実装する。

ユースケース層のビジネスロジックは以下の種類で実装する。
偵察・・・画面に表示するデータを取得する
順次・・・ビジネスのワークフローを順番通りに進める
ゲリラ・・・不定期に行われるイベント処理を実施する
監視・・・DB内部の情報を監視し、アプリの規約に違反したユーザーの発見などを行う
エスカレーション・・・アプリのルールに違反しているユーザーへの通知、警告などを行う
エマージェンシ・・・緊急事態に対応する
相対・・・キャンセル処理などビジネスのワークフローから外れる例外処理

3.ミッション層

具体的なビジネスロジックを記述するための層。ビジネスロジックはDCIアーキテクチャによって実装が行われる。
この層では、ユースケース層で実現したい目的をドメイン層のメソッドを組み合わせて実現する。
以下の観点で、ドメイン層のメソッドを組み合わせロジックを作成する。

イベント・・・DBへデータを書き込む事象
リソース・・・企業が取り扱うサービス or 商品
ルール・・・イベントに紐づく運用ルール
テクノロジー・・外部ライブラリを使用して実装されるメールの転送やファイルアップロード
エージェント・・・ミッション層のロジックを統率する。イベント、リソース、テクノロジーのメソッドが全て集約する。

4.ドメイン層

ミッション層のロジックを構成する層。ミッション層内でメソッドの呼び出しが行われる。
ドメイン層では、アプリ全体で共通するデータ構造の取得、共通するデータ構造に依存した業務ルールの提供、データ集約に基づいたデータ登録 or 更新 or 削除処理を取り扱う。

5.インフラ層

外部ライブラリを使用したロジックを記述し、全レイヤーに外部ライブラリの機能を提供する。例としてメールの送信、ログの出力などが該当する。
ライブラリはバージョンアップや使用するライブラリ変更されるなど、頻繁に発生する箇所する。そのため、DIP(依存関係逆転の原則)に従って、
ドメイン層にインターフェースを配置し、実際のロジックはインフラ層に記述する。インターフェースをデータインジェクション(依存性注入)機能を利用してインタンス生成を行う。

実装するクラス一覧

各レイヤー層に記述するクラス一覧について記載

1.コントロール層

Controller・・・REST APIの提供、Convetクラスのメソッドの呼び出し、ユースケース層の画面レベルのメソッドの呼び出し、
Web用、Andoroid、iOSへ引数を返却する際の値変換(JSON化)など
Coverter・・・REST APIから取得した画面の入力値の値をユースケース層へ引き渡す際の値の変換

2.ユースケース層

Bean・・・コントロール層から取得した入力値を格納して、ユースケース層へ引き渡す際のクラス。getter setterでメソッドを構成。表示画面と1対1の関係にある。
SearchOperation・・・検索したデータを画面表示することを目的としたビジネスロジック
RegularOperation・・・ビジネスのワークフローを次状態に進めるデータ登録、更新処理を目的としたビジネスロジック
GuerrillaOperation・・・ゲリライベントで発生するデータ登録、更新、削除処理を目的としたビジネスロジック
MonitoringOperation・・・DBのデータを監視し、規約違反が発見した場合、規約違反のデータ登録、更新、削除処理を目的としたビジネスロジック
EscalationOperation・・・エスカレーションラダーを設定し、規約違反を起こしているユーザーに各エスカレーションに応じた通知処理を目的としたビジネスロジック
EmergencyOperation・・・緊急事態対応を目的としたビジネスロジック
IrregularOperation・・・従来のビジネスのワークフローから外れるデータ登録、更新、削除処理を目的としたビジネスロジック

3.ミッション層

Mission ・・・Beanクラスを引数として受け取り、Beanクラスに対応したActorクラスのインスタンス、Actorクラスの型となるRoleインターフェスの選択、Roleメソッドの実行を行う。
Actor・・・Roleインターフェースを実装するクラス。Mixinを使用して多重継承を実施。
Role・・・インターフェース。ミッション層のEvent、Resource、Rule、Technologyのメソッドを集約する。ミッション層のロジックを組み合わせ ユースケース層で実現したいロジックを実装する。
Event・・・DBの登録 or 更新 or 削除に関する事象を実装するクラス。ドメイン層の、Aggregates、Servicesの組み合わせで実装される。
Resource・・・企業が取り扱う商品 or サービスのデータをDBから取得するクラス。ドメイン層の、Value Objects、Factories、Entities、Repositoriesの組み合わせで実装される。
Rule・・・運用ルールを実現するクラス。ドメイン層の、Specification、Value Objects、Factories、Entities、Repositoriesなどのメソッドの組み合わせで運用ルールを実現する。
Technology・・・技術的なビジネスロジックを実装するクラス。インフラ層のメソッドとインフラ層のメソッドを組み合わせて実現する。

4.ドメイン層

Factorys・・・DTOクラスを引数とし、ValueObjectインスタンスを生成
ValueObject・・・共通構造クラス。DTOをフィールド変数として持ち、getterメソッドとisメソッドのみで構成されている。DTOのデータを加工して提供、DTOのデータに応じたif分の提供などを行う。
Specifications・・・ビジネスルール or 入力値チェックなどのビジネスロジックを提供する
Repositorys・・・DBからデータ検索処理を行うクラス。
Aggregates・・・特定のデータ範囲に応じて、データの登録、更新、削除を行うクラス。
Services・・・上記で上げたドメイン層のロジックに分類できない処理を取り扱うクラス。

5.ドメイン層

Mail・・・メール送信を行う。
SFTP・・・SFTPによるデータ送受信を行う。
FileUpload・・・ファイルアップロードを行う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む