apollo-reference-architecture
Apollo.ioの参照アーキテクチャを実装します。Apollo統合の設計、パターンの確立、本番環境レベルの営業インテリジェンスシステムの構築時に活用できます。「apolloアーキテクチャ」「apolloシステム設計」「apollo統合パターン」「apolloベストプラクティスアーキテクチャ」などのフレーズで起動します。
description の原文を見る
Implement Apollo.io reference architecture. Use when designing Apollo integrations, establishing patterns, or building production-grade sales intelligence systems. Trigger with phrases like "apollo architecture", "apollo system design", "apollo integration patterns", "apollo best practices architecture".
SKILL.md 本文
Apollo リファレンスアーキテクチャ
概要
Apollo.io インテグレーションのための本番対応リファレンスアーキテクチャです。システム設計、データフロー、インテグレーションパターンをカバーしています。
アーキテクチャ図
+------------------+ +------------------+ +------------------+
| Frontend | | API Gateway | | Apollo API |
| (React/Vue) |---->| (Express) |---->| (External) |
+------------------+ +------------------+ +------------------+
| |
v |
+------------------+ |
| Apollo Service |<----------------+
| (Business Logic)|
+------------------+
| | |
+-------------+ | +-------------+
v v v
+------------+ +------------+ +------------+
| Cache | | Database | | Queue |
| (Redis) | | (Postgres) | | (Bull) |
+------------+ +------------+ +------------+
プロジェクト構造
src/
├── lib/
│ └── apollo/
│ ├── client.ts # Apollo API クライアント
│ ├── cache.ts # キャッシング層
│ ├── rate-limiter.ts # レート制限
│ ├── errors.ts # カスタムエラー
│ └── types.ts # TypeScript 型
├── services/
│ └── apollo/
│ ├── search.service.ts # ユーザー/組織検索
│ ├── enrich.service.ts # エンリッチメントロジック
│ ├── sequence.service.ts # メールシーケンス
│ └── sync.service.ts # データ同期
├── jobs/
│ └── apollo/
│ ├── enrich.job.ts # バックグラウンドエンリッチメント
│ ├── sync.job.ts # 定期同期
│ └── cleanup.job.ts # キャッシュクリーンアップ
├── routes/
│ └── api/
│ └── apollo/
│ ├── search.ts # 検索エンドポイント
│ ├── enrich.ts # エンリッチメントエンドポイント
│ └── webhooks.ts # Webhook ハンドラー
├── models/
│ ├── contact.model.ts # 連絡先エンティティ
│ ├── company.model.ts # 企業エンティティ
│ └── engagement.model.ts # メールエンゲージメント
└── config/
└── apollo.config.ts # Apollo 設定
コアコンポーネント
1. Apollo サービスレイヤー
// src/services/apollo/apollo.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApolloClient } from '../../lib/apollo/client';
import { ApolloCache } from '../../lib/apollo/cache';
import { Contact } from '../../models/contact.model';
import { Company } from '../../models/company.model';
@Injectable()
export class ApolloService {
constructor(
private readonly client: ApolloClient,
private readonly cache: ApolloCache,
@InjectRepository(Contact)
private readonly contactRepo: Repository<Contact>,
@InjectRepository(Company)
private readonly companyRepo: Repository<Company>,
) {}
async searchAndEnrich(criteria: SearchCriteria): Promise<EnrichedLead[]> {
// 1. Apollo を検索
const searchResults = await this.client.searchPeople(criteria);
// 2. フィルタリングとスコアリング
const qualified = this.qualifyLeads(searchResults.people, criteria);
// 3. トップリードをエンリッチ
const enriched = await Promise.all(
qualified.slice(0, 25).map(lead => this.enrichLead(lead))
);
// 4. データベースに永続化
await this.persistLeads(enriched);
return enriched;
}
private async enrichLead(lead: RawLead): Promise<EnrichedLead> {
// キャッシュをチェック
const cached = await this.cache.get(`lead:${lead.id}`);
if (cached) return cached;
// Apollo からエンリッチ
const [personData, companyData] = await Promise.all([
this.client.enrichPerson({ id: lead.id }),
lead.organization?.primary_domain
? this.client.enrichOrganization(lead.organization.primary_domain)
: null,
]);
const enriched = this.mergeData(lead, personData, companyData);
// 結果をキャッシュ
await this.cache.set(`lead:${lead.id}`, enriched, 86400); // 24時間
return enriched;
}
private async persistLeads(leads: EnrichedLead[]): Promise<void> {
for (const lead of leads) {
// 連絡先をアップサート
await this.contactRepo.upsert({
apolloId: lead.id,
email: lead.email,
name: lead.name,
title: lead.title,
linkedinUrl: lead.linkedinUrl,
companyId: lead.company?.id,
enrichedAt: new Date(),
}, ['apolloId']);
// 企業をアップサート
if (lead.company) {
await this.companyRepo.upsert({
apolloId: lead.company.id,
name: lead.company.name,
domain: lead.company.domain,
industry: lead.company.industry,
employeeCount: lead.company.employeeCount,
enrichedAt: new Date(),
}, ['apolloId']);
}
}
}
}
2. バックグラウンドジョブ処理
// src/jobs/apollo/enrich.job.ts
import { Job, Queue } from 'bull';
import { Process, Processor } from '@nestjs/bull';
import { ApolloService } from '../../services/apollo/apollo.service';
interface EnrichJobData {
contactIds: string[];
priority: 'high' | 'normal' | 'low';
}
@Processor('apollo-enrich')
export class EnrichProcessor {
constructor(private readonly apolloService: ApolloService) {}
@Process('enrich-contacts')
async handleEnrich(job: Job<EnrichJobData>): Promise<void> {
const { contactIds, priority } = job.data;
// レート制限を守るためバッチで処理
const batchSize = priority === 'high' ? 10 : 5;
for (let i = 0; i < contactIds.length; i += batchSize) {
const batch = contactIds.slice(i, i + batchSize);
await Promise.all(
batch.map(id => this.apolloService.enrichContact(id))
);
// 進捗を更新
await job.progress(((i + batchSize) / contactIds.length) * 100);
// レート制限の遅延
if (i + batchSize < contactIds.length) {
await new Promise(r => setTimeout(r, 1000));
}
}
}
}
// キュープロデューサー
@Injectable()
export class EnrichQueue {
constructor(@InjectQueue('apollo-enrich') private queue: Queue) {}
async enqueueContacts(contactIds: string[], priority: 'high' | 'normal' | 'low' = 'normal') {
await this.queue.add('enrich-contacts', {
contactIds,
priority,
}, {
priority: priority === 'high' ? 1 : priority === 'normal' ? 5 : 10,
attempts: 3,
backoff: {
type: 'exponential',
delay: 5000,
},
});
}
}
3. データモデル
// src/models/contact.model.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, Index } from 'typeorm';
import { Company } from './company.model';
@Entity('contacts')
export class Contact {
@PrimaryGeneratedColumn('uuid')
id: string;
@Index({ unique: true })
@Column()
apolloId: string;
@Index()
@Column({ nullable: true })
email: string;
@Column()
name: string;
@Column({ nullable: true })
firstName: string;
@Column({ nullable: true })
lastName: string;
@Column({ nullable: true })
title: string;
@Column({ nullable: true })
seniority: string;
@Column({ nullable: true })
linkedinUrl: string;
@Column({ nullable: true })
phone: string;
@Column({ type: 'jsonb', nullable: true })
customFields: Record<string, any>;
@ManyToOne(() => Company, company => company.contacts)
company: Company;
@Column()
companyId: string;
@Column({ type: 'timestamp' })
enrichedAt: Date;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updatedAt: Date;
}
// src/models/company.model.ts
@Entity('companies')
export class Company {
@PrimaryGeneratedColumn('uuid')
id: string;
@Index({ unique: true })
@Column()
apolloId: string;
@Column()
name: string;
@Index()
@Column()
domain: string;
@Column({ nullable: true })
industry: string;
@Column({ nullable: true })
subIndustry: string;
@Column({ nullable: true })
employeeCount: number;
@Column({ nullable: true })
annualRevenue: number;
@Column({ nullable: true })
foundedYear: number;
@Column({ type: 'text', nullable: true })
description: string;
@Column({ type: 'jsonb', nullable: true })
technologies: string[];
@Column({ type: 'jsonb', nullable: true })
location: {
city: string;
state: string;
country: string;
};
@OneToMany(() => Contact, contact => contact.company)
contacts: Contact[];
}
4. API ルート
// src/routes/api/apollo/search.ts
import { Router } from 'express';
import { ApolloService } from '../../../services/apollo/apollo.service';
import { validateRequest } from '../../../middleware/validation';
const router = Router();
router.post('/search', validateRequest(SearchSchema), async (req, res) => {
const { domains, titles, locations, minEmployees, maxEmployees } = req.body;
const results = await apolloService.searchAndEnrich({
domains,
titles,
locations,
minEmployees,
maxEmployees,
});
res.json({
success: true,
data: results,
meta: {
count: results.length,
timestamp: new Date().toISOString(),
},
});
});
router.post('/enrich/bulk', validateRequest(BulkEnrichSchema), async (req, res) => {
const { contactIds, priority } = req.body;
// バックグラウンド処理のためキューに登録
await enrichQueue.enqueueContacts(contactIds, priority);
res.json({
success: true,
message: `Queued ${contactIds.length} contacts for enrichment`,
jobId: 'job-id-here',
});
});
export default router;
インテグレーションパターン
CRM インテグレーション (Salesforce)
// src/integrations/salesforce.ts
export class SalesforceIntegration {
async syncContact(contact: Contact): Promise<void> {
const sfContact = await this.salesforce.sobject('Contact').upsert({
Email: contact.email,
FirstName: contact.firstName,
LastName: contact.lastName,
Title: contact.title,
Apollo_ID__c: contact.apolloId,
LinkedIn_URL__c: contact.linkedinUrl,
}, 'Email');
console.log(`Synced contact ${contact.email} to Salesforce`);
}
async syncCompany(company: Company): Promise<void> {
const sfAccount = await this.salesforce.sobject('Account').upsert({
Name: company.name,
Website: `https://${company.domain}`,
Industry: company.industry,
NumberOfEmployees: company.employeeCount,
Apollo_ID__c: company.apolloId,
}, 'Website');
}
}
イベント駆動型アーキテクチャ
// src/events/apollo.events.ts
export const APOLLO_EVENTS = {
CONTACT_ENRICHED: 'apollo.contact.enriched',
COMPANY_ENRICHED: 'apollo.company.enriched',
SEARCH_COMPLETED: 'apollo.search.completed',
SEQUENCE_STARTED: 'apollo.sequence.started',
EMAIL_ENGAGEMENT: 'apollo.email.engagement',
};
// イベントハンドラー
eventBus.on(APOLLO_EVENTS.CONTACT_ENRICHED, async (contact) => {
// CRM に同期
await salesforceIntegration.syncContact(contact);
// 検索インデックスを更新
await searchIndex.indexContact(contact);
// 関連チームに通知
if (contact.score >= 80) {
await slackNotifier.sendHighValueLead(contact);
}
});
成果物
- レイヤー化されたアーキテクチャ (クライアント、サービス、ジョブ、モデル)
- Bull によるバックグラウンドジョブ処理
- TypeORM によるデータベースモデル
- RESTful API エンドポイント
- CRM インテグレーションパターン
- イベント駆動型アーキテクチャ
エラーハンドリング
| レイヤー | 戦略 |
|---|---|
| クライアント | バックオフで再試行 |
| サービス | グレースフルデグラデーション |
| ジョブ | デッドレターキュー |
| API | 構造化されたエラーレスポンス |
リソース
次のステップ
環境設定については apollo-multi-env-setup に進んでください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- Brmbobo
- リポジトリ
- Brmbobo/Web2podcast
- ライセンス
- MIT
- 最終更新
- 2026/1/26
Source: https://github.com/Brmbobo/Web2podcast / ライセンス: MIT
関連スキル
3-statement-model
3種類の財務諸表テンプレート(損益計算書、貸借対照表、キャッシュフロー計算書)を作成・記入・完成させることができます。モデルテンプレートの記入、既存のモデル枠組みの完成、財務モデルへのデータ入力、部分的に完成した損益/貸借/キャッシュフロー枠組みの完成、または既存テンプレート構造内での統合財務諸表の連携に対応しています。3種類の財務モデルテンプレートの記入、完成、またはデータ入力に関するご依頼で自動的に機能します。
strategic-decision
CEO・経営層向けの戦略的意思決定支援です。前提条件に異議を唱え、問題を診断し、確実な戦略を設計できます。4つのモード(AGGRESSIVE:大きな夢を見る、SELECTIVE:基盤を維持しつつ有望な拡張を厳選、DIAGNOSTIC:最大限の厳密性、VALIDATION:本質に絞る)を備えています。創業者、経営幹部、プロダクトリーダーが製品開発、成長戦略、市場戦略、技術選定、リソース配分に関する戦略的判断が必要な場面で活用できます。
value-realization
エンドユーザーが製品アイデアから明確な価値を感じるかどうかを分析します。以下の場面で活用できます:製品コンセプトの議論、機能の評価、製品改善の方向性提示、マーケティング戦略の企画、導入・継続率の問題分析、コピーが価値を伝えているかの検証、機能と利用シーンの対応付け、または製品方向性・ポジショニング・エンドユーザーの需要の有無が不確かな場合(例:「これは良いアイデアか」「この製品をどう思うか」「ユーザーは必要とするか」「この機能は何に役立つのか」「機能の価値をどう説明するか」「このコピーをどう思うか」「利用シーンを作成する手助けが欲しい」「ユーザーが継続利用しない理由は何か」「どうポジショニングすべきか」)。
creating-financial-models
このスキルは、投資判断に必要な高度な財務モデリング機能を提供します。DCF分析、感度分析、モンテカルロシミュレーション、シナリオプランニングなど、複数の分析手法を組み合わせることで、より正確で信頼性の高い財務予測が可能になります。
pestel-analysis
政治的、経済的、社会的、技術的、環境的、法的な外部要因を分析します。市場環境の変化が製品、ロードマップ、または戦略に大きな影響を与える可能性がある場合に活用できます。
chemical_safety_assessment
化学安全性評価 - 化学物質の安全性を評価します。PubChemの化合物情報、FDAの医薬品データ、ADMET予測、ChEMBLの構造警告を活用します。このスキルを使用することで、化合物名から一般情報を取得したり、医薬品名から警告および注意事項を取得したり、分子のADMETを予測したり、化合物の構造警告を検出したりできます。4つのSCPサーバーから4つのツールを統合しています。