NestJS クリーンアーキテクチャ:UseCase/Domain/Adapter を疎結合に保つ設計術

NestJS でアプリケーションを開発していると、ビジネスロジックとインフラストラクチャが密結合してしまい、テストが難しくなったり、仕様変更時の影響範囲が広がったりすることはないでしょうか。 クリーンアーキテクチャを導入すれば、UseCase・Domain・Adapter の各層を疎結合に保ち、保守性とテスタビリティの高いコードを実現できます。 本記事では、NestJS におけるクリーンアーキテクチャの実装方法を、具体的なコード例とともに詳しく解説していきますね。
背景
クリーンアーキテクチャとは
クリーンアーキテクチャは、ソフトウェアを「ビジネスルール(Domain)」「アプリケーションロジック(UseCase)」「外部インターフェース(Adapter)」という層に分離する設計思想です。 各層が明確な責務を持ち、依存関係を一方向に保つことで、変更に強く、テストしやすいシステムを構築できるのが特徴でしょう。
以下の図は、クリーンアーキテクチャの基本的な層構造を示しています。
mermaidflowchart TB
subgraph outer["外部層(Adapter)"]
controller["Controller<br/>REST/GraphQL"]
repo["Repository実装<br/>DB/API"]
end
subgraph middle["アプリケーション層(UseCase)"]
usecase["UseCase<br/>ビジネスフロー"]
end
subgraph inner["内部層(Domain)"]
entity["Entity<br/>ビジネスルール"]
port["Port(Interface)<br/>抽象化"]
end
controller -->|呼び出し| usecase
usecase -->|利用| entity
usecase -->|依存| port
repo -.->|実装| port
style inner fill:#e1f5ff
style middle fill:#fff4e1
style outer fill:#ffe1e1
上記の図で示したように、内側(Domain)は外側(Adapter)を知らず、外側が内側に依存するという依存性逆転の原則が守られています。 これにより、データベースや外部 API の変更がビジネスロジックに影響を与えないのです。
NestJS でクリーンアーキテクチャを採用する利点
NestJS は依存性注入(DI)の仕組みを標準で備えているため、クリーンアーキテクチャとの相性が抜群です。 プロバイダーやモジュールを活用することで、Interface(Port)と実装(Adapter)を簡単に切り替えられ、テスト時にはモックを注入するといった運用がスムーズに行えます。
# | 項目 | 説明 |
---|---|---|
1 | テスタビリティ | UseCase が Port(Interface)に依存するため、モックを簡単に注入可能 |
2 | 保守性 | 各層の責務が明確で、変更時の影響範囲を限定できる |
3 | 拡張性 | 新しい Adapter(例:別の DB、外部 API)を追加しても UseCase を変更不要 |
4 | チーム開発 | 層ごとに担当を分けやすく、並行開発が効率化される |
課題
よくある密結合の問題
クリーンアーキテクチャを導入しない場合、以下のような問題が発生しがちです。
1. UseCase が具体的な実装に直接依存
UseCase の中で TypeORM のリポジトリや Prisma クライアントを直接利用してしまうと、データベースの変更時に UseCase 自体を書き換える必要が生じます。 これではビジネスロジックとインフラストラクチャが密結合し、テストも困難になってしまうでしょう。
2. ビジネスルールが Controller に散在
認証や権限チェック、バリデーションといったビジネスルールを Controller 層で記述してしまうケースも多く見られます。 このような設計では、同じルールを別のエンドポイントで再利用する際にコードの重複が発生し、保守性が低下してしまいますね。
3. テストのためにデータベースを起動する必要がある
UseCase が具体的な DB 実装に依存していると、ユニットテストを実行するたびに実際のデータベースを起動しなければなりません。 テストの実行速度が遅くなり、CI/CD パイプラインのボトルネックになる可能性があります。
以下の図は、密結合が発生している典型的な構成です。
mermaidflowchart LR
ctrl["Controller"] -->|直接依存| typeorm["TypeORM<br/>Repository"]
ctrl -->|直接依存| prisma["Prisma<br/>Client"]
ctrl -->|ビジネスロジック| validation["バリデーション<br/>認証処理"]
style ctrl fill:#ffcccc
style typeorm fill:#ffcccc
style prisma fill:#ffcccc
上記のように、Controller が複数の具体実装に直接依存してしまうと、変更の影響範囲が広がり、テストも複雑化します。
疎結合を実現するための要件
課題を解決し、疎結合なアーキテクチャを実現するには、以下の要件を満たす必要があります。
# | 要件 | 目的 |
---|---|---|
1 | Port(Interface)の定義 | UseCase が具体実装ではなく抽象に依存する |
2 | Adapter の実装分離 | データベースや外部 API の実装を差し替え可能にする |
3 | Dependency Injection の活用 | NestJS のプロバイダー機能で Port と Adapter を紐付ける |
4 | Domain の独立性確保 | Entity やビジネスルールが外部技術に依存しない |
解決策
層ごとの責務を明確化
クリーンアーキテクチャでは、各層が以下の責務を担います。
Domain 層(内部層)
- Entity: ビジネスルールを持つドメインオブジェクト。外部技術に一切依存しません。
- Port(Interface): データアクセスや外部サービスとの通信を抽象化したインターフェース。
UseCase 層(アプリケーション層)
- UseCase: ビジネスフローを実装するクラス。Port を通じて外部と通信し、Entity を操作します。
Adapter 層(外部層)
- Controller: HTTP リクエストを受け取り、UseCase を呼び出します。
- Repository: データベースや外部 API との実際の通信を実装し、Port を実装します。
以下の図は、各層の依存関係を示したものです。
mermaidflowchart TB
subgraph adapter["Adapter層"]
controller["UserController"]
repository["UserRepositoryImpl"]
end
subgraph usecase["UseCase層"]
uc["CreateUserUseCase"]
end
subgraph domain["Domain層"]
entity["User Entity"]
port["IUserRepository<br/>(Port)"]
end
controller -->|呼び出し| uc
uc -->|依存| port
uc -->|利用| entity
repository -.->|実装| port
style domain fill:#d4edda
style usecase fill:#fff3cd
style adapter fill:#f8d7da
上記の構成により、UseCase は Port のみに依存し、具体的な Repository 実装を知る必要がありません。
Port(Interface)の定義
まず、Domain 層に Port を定義します。 Port は、UseCase が必要とするデータアクセスや外部サービスのメソッドを抽象化したインターフェースです。
typescript// src/domain/ports/user-repository.port.ts
import { User } from '../entities/user.entity';
export interface IUserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<User>;
delete(id: string): Promise<void>;
}
上記のコードでは、IUserRepository
という Port を定義しています。
このインターフェースを通じて、UseCase はユーザーデータの取得・保存・削除を行いますが、具体的な実装方法(TypeORM、Prisma、など)は知りません。
Entity の実装
次に、ビジネスルールを持つ Entity を定義しましょう。 Entity は外部技術に依存せず、純粋な TypeScript クラスとして実装します。
typescript// src/domain/entities/user.entity.ts
export class User {
constructor(
public readonly id: string,
public name: string,
public email: string,
private _isActive: boolean = true
) {
this.validateEmail(email);
}
// ビジネスルール:メールアドレスの形式チェック
private validateEmail(email: string): void {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
}
// ビジネスルール:アクティブ状態の変更
deactivate(): void {
this._isActive = false;
}
activate(): void {
this._isActive = true;
}
get isActive(): boolean {
return this._isActive;
}
}
このコードでは、User
Entity がメールアドレスのバリデーションやアクティブ状態の管理といったビジネスルールをカプセル化しています。
データベースの技術的な詳細(カラム定義、マッピングなど)は一切含まれていません。
UseCase の実装
UseCase は、Port を通じてデータアクセスを行い、Entity を操作してビジネスフローを実現します。
typescript// src/application/use-cases/create-user.use-case.ts
import { Injectable, Inject } from '@nestjs/common';
import { IUserRepository } from '../../domain/ports/user-repository.port';
import { User } from '../../domain/entities/user.entity';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class CreateUserUseCase {
constructor(
@Inject('IUserRepository')
private readonly userRepository: IUserRepository
) {}
async execute(
name: string,
email: string
): Promise<User> {
// 1. 新しいユーザーエンティティを作成
const user = new User(uuidv4(), name, email);
// 2. ビジネスルールの確認(例:重複チェック)
const existingUser = await this.userRepository.findById(
user.id
);
if (existingUser) {
throw new Error('User already exists');
}
// 3. リポジトリを通じて永続化
return await this.userRepository.save(user);
}
}
上記の CreateUserUseCase
は、IUserRepository
という Port に依存しており、具体的な実装(TypeORM、Prisma など)を知りません。
このため、テスト時にはモックリポジトリを簡単に注入できます。
Adapter(Repository)の実装
Adapter 層では、Port を実装した具体的なクラスを作成します。 ここでは TypeORM を使った例を示しますが、Prisma や他の ORM に切り替える場合も、この層だけを変更すれば済みます。
typescript// src/infrastructure/repositories/user-repository.impl.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { IUserRepository } from '../../domain/ports/user-repository.port';
import { User } from '../../domain/entities/user.entity';
import { UserEntity } from '../entities/user.entity.typeorm';
@Injectable()
export class UserRepositoryImpl implements IUserRepository {
constructor(
@InjectRepository(UserEntity)
private readonly repository: Repository<UserEntity>
) {}
async findById(id: string): Promise<User | null> {
const userEntity = await this.repository.findOne({
where: { id },
});
if (!userEntity) return null;
// TypeORM エンティティから Domain エンティティへ変換
return new User(
userEntity.id,
userEntity.name,
userEntity.email,
userEntity.isActive
);
}
async save(user: User): Promise<User> {
const userEntity = this.repository.create({
id: user.id,
name: user.name,
email: user.email,
isActive: user.isActive,
});
await this.repository.save(userEntity);
return user;
}
async delete(id: string): Promise<void> {
await this.repository.delete(id);
}
}
このコードでは、TypeORM の Repository
を使ってデータベース操作を実装しつつ、Domain の User
Entity との変換を行っています。
UseCase は UserRepositoryImpl
の存在を知らず、IUserRepository
を通じてのみアクセスするため、疎結合が保たれますね。
TypeORM Entity の定義
TypeORM 用の Entity は、インフラストラクチャ層に配置します。 これは、データベースのテーブル構造を表現する技術的な詳細であり、Domain の Entity とは別物です。
typescript// src/infrastructure/entities/user.entity.typeorm.ts
import { Entity, Column, PrimaryColumn } from 'typeorm';
@Entity('users')
export class UserEntity {
@PrimaryColumn()
id: string;
@Column()
name: string;
@Column()
email: string;
@Column({ default: true })
isActive: boolean;
}
上記の UserEntity
は TypeORM のデコレーターを使用しており、Domain 層の User
とは完全に分離されています。
Module での依存性注入の設定
NestJS のモジュールシステムを使い、Port と Adapter を紐付けます。
typescript// src/user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserEntity } from './infrastructure/entities/user.entity.typeorm';
import { UserRepositoryImpl } from './infrastructure/repositories/user-repository.impl';
import { CreateUserUseCase } from './application/use-cases/create-user.use-case';
import { UserController } from './presentation/controllers/user.controller';
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
providers: [
{
provide: 'IUserRepository',
useClass: UserRepositoryImpl,
},
CreateUserUseCase,
],
controllers: [UserController],
})
export class UserModule {}
このモジュール設定により、IUserRepository
という名前で UserRepositoryImpl
が注入されます。
UseCase は @Inject('IUserRepository')
でこの実装を受け取るため、具体的なクラス名を知る必要がありません。
Controller の実装
Controller は HTTP リクエストを受け取り、UseCase を呼び出すだけのシンプルな役割に徹します。
typescript// src/presentation/controllers/user.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserUseCase } from '../../application/use-cases/create-user.use-case';
@Controller('users')
export class UserController {
constructor(
private readonly createUserUseCase: CreateUserUseCase
) {}
@Post()
async createUser(
@Body() body: { name: string; email: string }
) {
const user = await this.createUserUseCase.execute(
body.name,
body.email
);
return {
id: user.id,
name: user.name,
email: user.email,
isActive: user.isActive,
};
}
}
上記のコードでは、Controller はビジネスロジックを一切持たず、UseCase に処理を委譲しています。 これにより、Controller のテストも簡潔になります。
具体例
ディレクトリ構成
クリーンアーキテクチャを採用した NestJS プロジェクトのディレクトリ構成例を示します。
csharpsrc/
├── domain/ # Domain層(内部層)
│ ├── entities/
│ │ └── user.entity.ts # ビジネスルールを持つEntity
│ └── ports/
│ └── user-repository.port.ts # Port(Interface)
│
├── application/ # UseCase層(アプリケーション層)
│ └── use-cases/
│ ├── create-user.use-case.ts
│ ├── get-user.use-case.ts
│ └── delete-user.use-case.ts
│
├── infrastructure/ # Adapter層(外部層)
│ ├── entities/
│ │ └── user.entity.typeorm.ts # TypeORM用Entity
│ └── repositories/
│ └── user-repository.impl.ts # Port実装
│
├── presentation/ # Adapter層(外部層)
│ └── controllers/
│ └── user.controller.ts # HTTP Controller
│
└── user.module.ts # DIの設定
このディレクトリ構成では、各層が明確に分離され、依存関係が一方向に保たれています。
以下の図は、具体的なクラス間の依存関係を示しています。
mermaidflowchart TB
subgraph presentation["Presentation(Controller)"]
userCtrl["UserController"]
end
subgraph application["Application(UseCase)"]
createUC["CreateUserUseCase"]
getUC["GetUserUseCase"]
end
subgraph domain["Domain"]
userEntity["User Entity"]
iUserRepo["IUserRepository<br/>(Port)"]
end
subgraph infrastructure["Infrastructure(Repository)"]
userRepoImpl["UserRepositoryImpl"]
typeormEntity["UserEntity<br/>(TypeORM)"]
end
userCtrl -->|呼び出し| createUC
userCtrl -->|呼び出し| getUC
createUC -->|依存| iUserRepo
createUC -->|利用| userEntity
getUC -->|依存| iUserRepo
userRepoImpl -.->|実装| iUserRepo
userRepoImpl -->|使用| typeormEntity
style domain fill:#d1ecf1
style application fill:#fff3cd
style infrastructure fill:#f8d7da
style presentation fill:#e2e3e5
上記の図により、UseCase が Port にのみ依存し、具体的な Repository 実装を知らないことが視覚的に理解できますね。
テストコードの実装
疎結合なアーキテクチャの最大の利点は、テストが容易になることです。 以下では、モックリポジトリを使った UseCase のユニットテスト例を示します。
モックリポジトリの作成
typescript// src/application/use-cases/__tests__/mocks/user-repository.mock.ts
import { IUserRepository } from '../../../domain/ports/user-repository.port';
import { User } from '../../../domain/entities/user.entity';
export class UserRepositoryMock implements IUserRepository {
private users: Map<string, User> = new Map();
async findById(id: string): Promise<User | null> {
return this.users.get(id) || null;
}
async save(user: User): Promise<User> {
this.users.set(user.id, user);
return user;
}
async delete(id: string): Promise<void> {
this.users.delete(id);
}
}
上記のモッククラスは、IUserRepository
を実装しており、インメモリで動作するため、データベースを起動する必要がありません。
UseCase のユニットテスト
typescript// src/application/use-cases/__tests__/create-user.use-case.spec.ts
import { Test } from '@nestjs/testing';
import { CreateUserUseCase } from '../create-user.use-case';
import { UserRepositoryMock } from './mocks/user-repository.mock';
describe('CreateUserUseCase', () => {
let useCase: CreateUserUseCase;
let repository: UserRepositoryMock;
beforeEach(async () => {
repository = new UserRepositoryMock();
const module = await Test.createTestingModule({
providers: [
CreateUserUseCase,
{
provide: 'IUserRepository',
useValue: repository,
},
],
}).compile();
useCase = module.get<CreateUserUseCase>(
CreateUserUseCase
);
});
it('should create a new user', async () => {
// Arrange
const name = 'John Doe';
const email = 'john@example.com';
// Act
const user = await useCase.execute(name, email);
// Assert
expect(user.name).toBe(name);
expect(user.email).toBe(email);
expect(user.isActive).toBe(true);
});
it('should throw error for invalid email', async () => {
// Arrange
const name = 'Jane Doe';
const invalidEmail = 'invalid-email';
// Act & Assert
await expect(
useCase.execute(name, invalidEmail)
).rejects.toThrow('Invalid email format');
});
});
このテストコードでは、モックリポジトリを注入することで、データベースに依存せずに UseCase の動作を検証しています。 テストの実行速度が速く、外部環境に影響されないため、CI/CD パイプラインでも安定して動作しますね。
複数の Adapter を切り替える例
疎結合な設計の利点を活かし、環境に応じて異なる Repository 実装を切り替える例を示します。
Prisma 実装の追加
typescript// src/infrastructure/repositories/user-repository-prisma.impl.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';
import { IUserRepository } from '../../domain/ports/user-repository.port';
import { User } from '../../domain/entities/user.entity';
@Injectable()
export class UserRepositoryPrismaImpl
implements IUserRepository
{
constructor(private readonly prisma: PrismaService) {}
async findById(id: string): Promise<User | null> {
const userRecord = await this.prisma.user.findUnique({
where: { id },
});
if (!userRecord) return null;
return new User(
userRecord.id,
userRecord.name,
userRecord.email,
userRecord.isActive
);
}
async save(user: User): Promise<User> {
await this.prisma.user.create({
data: {
id: user.id,
name: user.name,
email: user.email,
isActive: user.isActive,
},
});
return user;
}
async delete(id: string): Promise<void> {
await this.prisma.user.delete({ where: { id } });
}
}
上記の Prisma 実装も、IUserRepository
を実装しているため、UseCase を変更せずに利用できます。
環境変数による実装の切り替え
typescript// src/user.module.ts
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserEntity } from './infrastructure/entities/user.entity.typeorm';
import { UserRepositoryImpl } from './infrastructure/repositories/user-repository.impl';
import { UserRepositoryPrismaImpl } from './infrastructure/repositories/user-repository-prisma.impl';
import { CreateUserUseCase } from './application/use-cases/create-user.use-case';
import { UserController } from './presentation/controllers/user.controller';
import { PrismaService } from './infrastructure/prisma.service';
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
providers: [
{
provide: 'IUserRepository',
useFactory: (
configService: ConfigService,
prisma: PrismaService
) => {
const dbType = configService.get<string>('DB_TYPE');
if (dbType === 'prisma') {
return new UserRepositoryPrismaImpl(prisma);
}
// デフォルトはTypeORM
return new UserRepositoryImpl(/* TypeORM Repository */);
},
inject: [ConfigService, PrismaService],
},
CreateUserUseCase,
PrismaService,
],
controllers: [UserController],
})
export class UserModule {}
このモジュール設定では、環境変数 DB_TYPE
の値に応じて、TypeORM 実装または Prisma 実装を切り替えています。
UseCase や Controller は一切変更する必要がありません。
エラーハンドリングの実装
実際のアプリケーションでは、エラーハンドリングも重要です。 以下では、Domain 層でカスタムエラーを定義し、UseCase で適切に処理する例を示しましょう。
Domain エラーの定義
typescript// src/domain/errors/user.error.ts
export class UserNotFoundError extends Error {
constructor(id: string) {
super(`User with id ${id} not found`);
this.name = 'UserNotFoundError';
}
}
export class DuplicateUserError extends Error {
constructor(email: string) {
super(`User with email ${email} already exists`);
this.name = 'DuplicateUserError';
}
}
上記のカスタムエラーは、ビジネスルールの違反を表現するものです。
UseCase でのエラーハンドリング
typescript// src/application/use-cases/get-user.use-case.ts
import { Injectable, Inject } from '@nestjs/common';
import { IUserRepository } from '../../domain/ports/user-repository.port';
import { User } from '../../domain/entities/user.entity';
import { UserNotFoundError } from '../../domain/errors/user.error';
@Injectable()
export class GetUserUseCase {
constructor(
@Inject('IUserRepository')
private readonly userRepository: IUserRepository
) {}
async execute(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) {
throw new UserNotFoundError(id);
}
return user;
}
}
このコードでは、ユーザーが見つからない場合に UserNotFoundError
をスローしています。
Controller でのエラーハンドリング
typescript// src/presentation/controllers/user.controller.ts
import {
Controller,
Get,
Param,
NotFoundException,
} from '@nestjs/common';
import { GetUserUseCase } from '../../application/use-cases/get-user.use-case';
import { UserNotFoundError } from '../../domain/errors/user.error';
@Controller('users')
export class UserController {
constructor(
private readonly getUserUseCase: GetUserUseCase
) {}
@Get(':id')
async getUser(@Param('id') id: string) {
try {
const user = await this.getUserUseCase.execute(id);
return {
id: user.id,
name: user.name,
email: user.email,
isActive: user.isActive,
};
} catch (error) {
if (error instanceof UserNotFoundError) {
throw new NotFoundException(error.message);
}
throw error;
}
}
}
Controller では、Domain エラーを NestJS の HTTP 例外(NotFoundException
)に変換しています。
これにより、適切な HTTP ステータスコード(404)がクライアントに返されます。
まとめ
NestJS でクリーンアーキテクチャを実践することで、UseCase・Domain・Adapter の各層を疎結合に保ち、保守性とテスタビリティの高いアプリケーションを構築できます。 本記事では、Port(Interface)と Adapter の分離、依存性注入の活用、そして環境に応じた実装の切り替え方法を具体的なコード例とともに解説しました。
図で理解できる要点:
- クリーンアーキテクチャは内側(Domain)が外側(Adapter)を知らず、依存性が一方向に保たれる
- UseCase は Port に依存し、具体的な Repository 実装を知らないため、モック注入が容易
- 環境変数や Factory パターンで複数の Adapter を切り替え可能
以下の表は、クリーンアーキテクチャ導入時の主な効果をまとめたものです。
# | 効果 | 詳細 |
---|---|---|
1 | テストの高速化 | モックリポジトリでデータベース不要のユニットテストが可能 |
2 | 変更に強い設計 | データベースや外部 API の変更が UseCase に影響しない |
3 | ビジネスロジックの再利用 | Domain・UseCase が技術詳細から独立しているため再利用が容易 |
4 | チーム開発の効率化 | 層ごとに担当を分け、並行開発がスムーズになる |
疎結合な設計を実現するには、最初は多少のコード量が増えるかもしれませんが、長期的な保守性とテスト容易性を考えれば、非常に価値のある投資と言えるでしょう。 ぜひ、あなたの NestJS プロジェクトにもクリーンアーキテクチャを取り入れて、より堅牢で拡張性の高いアプリケーションを構築してみてください。
関連リンク
- article
NestJS クリーンアーキテクチャ:UseCase/Domain/Adapter を疎結合に保つ設計術
- article
NestJS Decorator 速見表:Controller/Param/Custom Decorator の定型パターン
- article
NestJS 最短セットアップ:Fastify + TypeScript + ESLint + Prettier を 10 分で
- article
NestJS × ExpressAdapter vs FastifyAdapter:レイテンシ/スループットを実測比較
- article
NestJS 依存循環(circular dependency)を断ち切る:ModuleRef と forwardRef の実戦対処
- article
NestJS アーキテクチャ超図解:DI コンテナ/プロバイダ/メタデータを一気に把握
- article
NestJS クリーンアーキテクチャ:UseCase/Domain/Adapter を疎結合に保つ設計術
- article
WebSocket プロトコル設計:バージョン交渉・機能フラグ・後方互換のパターン
- article
MySQL 読み書き分離設計:ProxySQL で一貫性とスループットを両立
- article
Motion(旧 Framer Motion)アニメオーケストレーション設計:timeline・遅延・相互依存の整理術
- article
WebRTC で遠隔支援:画面注釈・ポインタ共有・低遅延音声の実装事例
- article
JavaScript パフォーマンス最適化大全:レイアウトスラッシングを潰す実践テク
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来