序文
皆さんお久しぶりです!春採用からインターンシップの面接に履歴書を提出し、入社し、最終的に中途半端な後任研修生になることができたので、ここ数ヶ月更新していませんでした。
学校のインターンとして、ここ数カ月エンタープライズ開発環境でコードを書いた経験と合わせて、これから就職する、またはまだ働くことを楽しみにしている友人たちとこのことを共有したいと思います。 —エンタープライズレベルのプロジェクトとオンライン自習プロジェクトの違いは何ですか? 違いは何ですか?
会社の主流テクノロジースタック:
SpringBoot+Dubbo+Flink+Kalfk+MyBatisPlus+Mysql+Redis+Seata+MongoDB+ES+React+blockchain +人工知能
1. 複雑なビジネス ロジック
本番環境と開発環境の間にQA環境(品質保証)があることが多いのですが、入社初日にQA環境のデータベースに接続したときの第一印象は、「おい、この中にはこんなものがあるんだ」でした。フィールドが多すぎますね?
多数のフィールドが複雑なビジネス ロジックに対応しており、エンティティ クラスには単一の属性がなくなり、さらに多くの異なるエンティティが相互に結合され、マルチパーティ効果の結果が返されます
。 MybatisPlus のラッパーには特定の制限があり、テーブル ルックアップ メソッドを自分で手動でカプセル化する必要があります。たとえば、クエリでは、奇妙な関数、マルチテーブル クエリ、結合クエリ、サブクエリなどを使用する必要があります。
次に、開発プロセス中に作成した 2 つのマッパーを魅力的に示します。 1.
さまざまなマッパーを柔軟に使用する機能:
2. 複雑な条件判定:
3. 複雑なサブテーブル結合クエリ
2. 厳密なパラメータ検証
特殊データの形式については企業ごとに統一した要件があるため、フロントエンドコントローラ(Controller)では、受信したパラメータに対して対応するパラメータ検証を実行する必要があります 1. 時間型パラメータの検証 2. 携帯電話番号型
の
検証パラメータ
携帯電話番号のデータ フロントエンドが番号の検証に役立つかどうかを尋ねる人もいるでしょう。バックエンドが繰り返し動作する必要があるのはなぜですか? 実際、これは、テスト ケースが正常に実行されることを確認し、PostMan などのインターフェイス テスト ツールを使用した後にトークンを取得し、フロントエンドをバイパスして誤ったデータをバックエンドに要求することを回避する必要があるためです。 3.ページングパラメータ
の検証
ページングのサイズも妥当な範囲内に制御する必要があり、大きすぎる場合や不当な場合は予期しないエラーが発生します。たとえば、非常に大きな pageSize パラメータが渡され、ライブラリ内のデータ量が多い場合、1 回のリクエストで大量のデータが検出されます。何度も往復するとサーバーの帯域幅が大量に消費され、最終的にはサーバーがハングしてしまいます。
3. インターフェースのパフォーマンスに対する本番環境の要件
1. ライブラリの循環検索は許可されていません
自習プロジェクトの代表ともいえる有名な例、Riji Takeaway を例に、インターフェースの 1 つの記述方法を見てみましょう。
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
log.info("查询的名称是:{}", name);
//分页构造器
Page<Dish> dishPage = new Page<>(page, pageSize);
Page<DishDto> dishDtoPage = new Page<>();
//条件构造器
LambdaQueryWrapper<Dish> dishLqw = new LambdaQueryWrapper<>();
//模糊查询,将前端输入的名字和Dish表中的name进行模糊查询并添加name不为空的条件
dishLqw.like(name != null, Dish::getName, name);
dishLqw.orderByDesc(Dish::getUpdateTime);
//调用Service查询
dishService.page(dishPage, dishLqw);
BeanUtils.copyProperties(dishPage, dishDtoPage, "records");
List<Dish> records = dishPage.getRecords();
List<DishDto> dtoList = records.stream().map((temp) ->{
DishDto dishDto = new DishDto();
//再次拷贝,将普通属性拷贝给dishDto
BeanUtils.copyProperties(temp, dishDto);
Long categoryId = temp.getCategoryId(); //拿到分类id
// 拿到分类对象,根据id查到对象
Category category = categoryService.getById(categoryId);
if(category!=null) {
//避免出现空指针
//通过分类对象拿到name
String categoryName = category.getName();
//把name设置给dishDto
dishDto.setCategoryName(categoryName);//设置dishDto中CategoryName的值
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(dtoList);
return R.success(dishDtoPage);
}
注意深い学生であれば、このストリームではライブラリ検索操作 (i/o) がストリーム内の要素ごとに実行されていることがわかるでしょう。
Category category = categoryService.getById(categoryId);
データ量が非常に少ない場合、インターフェイスのパフォーマンスは影響を受けない可能性がありますが、ストリーム内のデータが大きい場合、そのようなインターフェイスは非常に長い時間がかかるため、運用環境では絶対に使用されません。 。これは、企業と独学の最大の違いの 1 つです。
したがって、そのような状況を避けるために、次のようなデータベースのチェック頻度を減らすために最善を尽くします (非常によく似たシーンであり、これは私が参加した最初のインターフェイスでもあります)。
一度にすべてチェックアウトできます。一度に 1 つずつチェックしないようにしてください。
将来、インターフェイスに循環対話型データベースが表示される場合、そのインターフェイスは癌である可能性があります。 2.マルチ
スレッド非同期処理
実際、一部のシナリオでは同期に対する強い要件がないため、同期処理が大幅に増加します。一部の「結果が返ってくるのを待つ必要がないところ」については、思い切って非同期処理を行うこともできます。
例を挙げると、現在のタスクは、未払いの請求書のバッチに基づいて所有者にメッセージをプッシュし、プッシュが完了するたびにリマインダーのステータスを更新することです。更新とプッシュの両方で準備する必要のある事前データが大量にあり、プッシュ ビジネスの呼び出しチェーンは非常に長くなります。シングルスレッド メソッドの場合、メソッドはインターフェイスを上から下まで実行し、約 5 ~ 6 秒かかります。非同期呼び出しメカニズムがなければ、メソッドの呼び出し元はプッシュ時間中に何も行うことができず、他のコードは一時停止ボタンを押した。しかし、スレッド プールを使用してワンクリックでメッセージを非同期にプッシュし、ステータスを更新すると、待ち時間が大幅に短縮され、インターフェイスの消費時間も 1 秒未満に短縮されます。
一部の特殊なシナリオでは、非同期メソッドまたは同期メソッドを使用することが非常に重要であることがわかります。マルチスレッドは非同期を実現するための手段の 1 つにすぎません。
4. コーディング標準
4.1 ストリーミングプログラミング
単一のマイクロサービスに基づくサブデータベースの機能により、強く関連する多くのデータが 1 つのデータベース内に存在しないため、複数テーブルの結合クエリの多くの機会が失われています。
それどころか、異なるマイクロサービス インターフェイスからの複数のデータを処理して組み立てることが重要であり、このプロセスではストリーミング プログラミングが無効になることはありません。
ストリーミング プログラミングを学び、それに慣れると、自分の開発が水を得た魚のように感じることができます。私はこれをストリーミング記述 + ラムダ式の伝承と呼びたいと思います。これはビジネス コードを簡素化するだけでなく、開発者の思考をより明確にします
。以下にいくつかの例を示します。ジョブに参加した後にストリーミング開発とラムダ式を使用したいくつかのシナリオ:
1. リストのフィルターと重複排除
List<Long> communityIds = transactionList.stream()
.map(TransactionCommunityVO::getPayeeCommunityId)
.filter(ObjectUtils::isNotEmpty).distinct()
.collect(Collectors.toList());
2. MyBatisPlus はラムダ式を使用して「選択ファジー結合クエリ」を実行します。
LambdaQueryChainWrapper<Device> deviceLambdaQueryChainWrapper = iDeviceService.lambdaQuery();
if (ObjectUtils.isNotEmpty(deviceQuery2.getFuzz())) {
deviceLambdaQueryChainWrapper.and(x -> {
x.like(Device::getName, deviceQuery2.getFuzz()).or().like(Device::getCode1, deviceQuery2.getFuzz());
});
}
3. 「あるフィールドの値とそのフィールドの出現数」をグループ化して<kv>マップの形式でリストに収集する
Map<Long, Long> idCountMap = deviceVo2s.stream()
.filter(index -> ObjectUtils.isNotEmpty(index.getOwnerId()))
.collect(Collectors.groupingBy(DeviceVo2::getOwnerId, Collectors.counting()));
4. ストリーム内の文字列を処理する
list= list.stream().map(x -> {
String[] strings = new String[x.length];
for (int i = 0; i < x.length; i++) {
if (null == x[i]) {
x[i] = "";
}
strings[i] = x[i].trim();
}
return strings;
}).collect(Collectors.toList());
5. フロー内で指定した条件でデータを収集する
Map<String, String> map2 = list4.stream().filter(temp -> "男".equals(temp.split("-")[1]))
.collect(Collectors.toMap(
temp -> temp.split("-")[0]
,
temp -> temp.split("-")[2]));
6. データ収集の条件
List<ChargingPileOrder> orders = taskItemAction.queryUserIdChargingWithUseChargingPileCounts(publisherIds);
Map<Long, String> resultMap = orders.stream()
//同一个用户可能使用不同充电桩并且使用相同次数的,查出来的数据已经按照时间拍好了序,
//流的作用,就是取同一用户下不同充电桩使用次数相同情况之中的最近使用的那条数据
.collect(Collectors.groupingBy(ChargingPileOrder::getPublisherId,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparing(ChargingPileOrder::getCreateTime)),
optional -> optional.map(ChargingPileOrder::getChargingPileName).orElse(""))));
4.2 再利用性の要件
優れたインターフェイスとは、既存の機能に限定されるものではなく、上位互換性を実現するものです。再利用性のレベルによって、ビジネス コードの長さが大きく決まります。ビジネス開発においては、最下層のインターフェースの左上に「○○回使用」と表示されているのをよく見かけますが、このようなインターフェースは再利用性が高いです。
コードの再利用性を高める方法として、私が感慨深いところが2つあります。
1. 不必要なパラメータの受け渡しを減らす:
渡す必要のあるパラメータが多数あるメソッドは、通常、読み取りが困難です。すべてのパラメータをオブジェクトにカプセル化してオブジェクトの転送を完了し、必要なときに直接取得するだけで済みます。これはエラー追跡にも役立ちます。多くの開発者は、オブジェクト ラッパーの層が多すぎるため、システム効率に影響を及ぼしています。しかし、それがもたらすメリットに比べれば、私たちはむしろパッケージングを行いたいと考えています。結局のところ、「カプセル化」も OOP の基本特性の 1 つであり、「各オブジェクトができるだけ少ない (そして単純な) 機能を実行する」も OOP の基本原則です。
2. 可能な限り切り離す:
「可変部分を不変部分から分離する」は、オブジェクト指向設計の 3 番目の原則です。継承再利用技術を利用すると、抽象基底クラスに不変部分を定義し、そのサブクラスで可変部分を実装できるため、不変部分を繰り返し定義する必要がなくなり、保守が容易になります。オブジェクト合成の再利用技術を使用すると、不変部分を定義し、可変部分をさまざまなコンポーネントで実装し、必要に応じて実行時に動的に構成できます。こうすることで、変動する部分に集中できる時間が増えます。オブジェクト合成技術は、各コンポーネントが比較的小さな機能しか果たさないため、コンポーネント間の結合が比較的緩やかで再利用率が高く、組み合わせることで新たな機能を得ることができます。
3. 基礎となるメソッドにあまり固定条件を記述しません。
特に Mapper または Wapper メソッドでは、制限されたステータス、タイプなどの不要な固定値クエリ条件をできるだけ少なく記述し、変更可能な転送パラメータはそのままにしておきます。上位互換になります。
4.3 列挙型クラスの使用
1 年前、私が初めて列挙を学んだとき、誰かが私に列挙のクラスについてどう思いますか、と尋ねました。仕様を定義するクラスであると言いましたが
、エンタープライズ開発ではそれがより明白になります。データベース内の一部のフィールドには多くの値が含まれることがよくあります。たとえば、テーブル内のこのフィールドの値はstatus
0、1、2、または 3 である可能性があります。異なる値は、このデータの異なる状態を表します。
データ処理の過程で、状態に制限がある場合は、xxx=0、xxx=2 を使用しないでください...代わりに、これはxxx=StatusEnum.ONLINE.getValue()
他の開発者が一目で理解できるようになるだけでなく、ある意味のデカップリングも実現します。の上 !
共通のものを 1 か所にまとめます。このシナリオは構成ファイルに非常に似ていませんか? IOC は最初は DOM4J と XML に依存していたと思います
4.4 クエリクラスの使用
エンタープライズ レベルのプロジェクトには、非常に複雑な要件があることがよくあります。たとえば、クエリ インターフェイスは多くのパラメーターを受け取ることがあります。これらのフィールド パラメーターはコントローラー層から取得され、層ごとに受け渡されます。これにより、実装メソッドが受信するという悪い現象が発生する可能性があります。パラメータが多すぎます!
このような:
PageInfo<Device> getDeviceList(Integer current, Integer size, List<Long> ownerIds, List<DeviceTypeEnum> typeList,
String code1, String code2, List<Integer> providerList, List<IsDeletedType> isDeletedList, List<Long> communityIdList,
String nameLike, List<Long> physicalCommunityIdList, List<Integer> statusList, List<Long> parentIdList, List<Long>
idList, List<String> areaPathList, List<String> sortList, Sort.Direction direction, List<String> communityPathList,
List<DeviceSubTypeEnum> deviceSubTypeEnumList, String code1Like, String code2Like);
仮引数長すぎませんか~ この書き方は「普通」に見えますが、呼び出すと非常に目立ちますし、引数を間違った位置に渡すとデバッグに時間がかかります!
したがって、このような問題を解決するために、クエリ クラスを使用してクエリを実行します (クエリ クラスはエンティティ クラスの属性フィールドをカプセル化し、set メソッドと get メソッドを通じて属性を操作します)。これにより、インターフェイス メソッドが見えるようになります。次のように、はるかに簡単になり、エラーの可能性が大幅に減少します。
4.5 安らぎのスタイル
SpringMVC を独学で勉強していた頃、RestFul 系のアプリケーションの話をよくしていましたが、当時はエンタープライズレベルのプロジェクトには触れていなかったので、そんなの関係ないと思っていましたが、実はそうではありません。インターフェースがそのようなスタイル仕様に従うことは非常に重要です。
クライアントは 4 つの HTTP 動詞を使用してサーバー側のリソースを操作し、「プレゼンテーション層の状態変換」を実現します。エンタープライズ レベルのアプリケーションでは、モジュール内に数十のコントローラーが存在することが多く、それらを参照すると、リクエスト メソッド (post/get/delete/update) と、リクエストパスの一般的な機能。RestFul スタイルは間違いなく優れた開発仕様です。
4.6 注意事項
最後になりましたが、重要なことを忘れない
でください。コードを書くこととコメントを書くことを忘れないでください。いわゆるコメントは決してコメントではなく、重要な箇所についてのステートメントです。
例:
1. インターフェイスの特定の機能の説明
2. 仮パラメータの受け渡しの説明
3. 戻り値の型と内容の説明
4. エンティティ クラスの属性フィールドの説明
5. 特別な用途の説明共通分野の