# The Definitive Framework for Technology Selection in Legacy-Industry DX: From a Real Example in the Lumber-Distribution Industry

> How do you achieve DX in a legacy industry where phone, fax, and Excel are the norm? From technology selection across TypeScript, Python, Golang, and AWS Terraform through implementation and operations, we present a practical framework based on a success story in the lumber-distribution industry.

- Published: 2025-01-08
- Author: 友田 陽大
- Tags: レガシー産業DX, 技術選定, 業界DX, デジタル変革, TypeScript, Python, Golang, AWS
- URL: https://tomodahinata.com/en/blog/legacy-industry-dx-technology-selection-framework
- Category: B2B SaaS & DX strategy
- Pillar guide: https://tomodahinata.com/en/blog/award-winning-b2b-saas-architecture-deep-dive

## Key points

- Technology selection for legacy-industry DX is evaluated on 5 axes: industry fit, non-engineer readiness, phased rollout, long-term maintainability, and cost optimization
- For authentication/authorization of 8 kinds of stakeholders, we chose AWS Cognito for its flexible custom attributes
- For heavy PDF/Excel batch processing we use Python (Flask + SQLAlchemy); for business documents we reproduce layouts with openpyxl + LibreOffice conversion
- Non-engineer readiness means designing a usable UI with Excel-like operability and clear error messages
- With a phased rollout, we let it coexist with existing operations and avoid a sudden full migration

---

# The Definitive Framework for Technology Selection in Legacy-Industry DX: From a Real Example in the Lumber-Distribution Industry

## Introduction: why legacy-industry DX is hard

Manufacturing, construction, logistics, agriculture/forestry/fisheries — these "legacy industries" still run on phone, fax, and Excel as the norm. Unable to receive the benefits of digitalization, they struggle with **inefficient workflows, knowledge siloed in individuals, and labor shortages.**

Over the past two years I took on the DX of the lumber-distribution industry, a "super-legacy industry," and developed a product that **won the METI Minister's Award.** In this article, I present the **"definitive framework for technology selection in legacy-industry DX"** derived from that experience.

In particular, I focus on the decision-making of technology selection — **"which technology should you choose"** — and provide practical guidelines.

---

## Framework overview: 5 evaluation axes

In technology selection for legacy-industry DX, we select technology on the following 5 evaluation axes.

| Evaluation axis | Importance | Description |
|--------|--------|------|
| **Industry fit** | ★★★★★ | Can it handle the industry-specific complexity (commerce flow, user attributes, workflows)? |
| **Non-engineer readiness** | ★★★★☆ | Can on-site non-engineers master it (UI/UX, manuals)? |
| **Phased rollout** | ★★★★☆ | Can it migrate gradually while coexisting with existing workflows? |
| **Long-term maintainability** | ★★★★★ | Can it endure 10+ years of long-term operation (technical debt, siloing risk)? |
| **Cost optimization** | ★★★☆☆ | Are the initial and running costs appropriate? |

---

## Case study: technology selection for lumber-distribution-industry DX

### Industry characteristics: 8 kinds of stakeholders and a complex commerce flow

In the lumber-distribution industry, the following 8 kinds of stakeholders exist.

```
Forestry → Market → Sawmill → Precut → Builder
                ↓
            Manufacturer → Wholesaler → Other
```

The following differ per stakeholder:
- **Executable features** (ordering, inventory management, quote creation)
- **Viewable information** (own inventory only, the market's entire inventory, a partner's inventory)
- **Pricing authority** (market price, wholesale price, retail price)

### The technology-selection decision process

#### Axis 1: Industry fit — AWS Cognito + custom logic

**Requirements:**
- Authentication/authorization per 8 kinds of user attributes
- Different permission management for each attribute

**Comparison of candidate technologies:**

| Technology | Pros | Cons | Verdict |
|------|---------|-----------|------|
| Firebase Auth | Easy, large free tier | Low flexibility of custom attributes | × |
| Auth0 | High flexibility | High cost (MAU billing) | △ |
| AWS Cognito | AWS integration, custom attributes | Learning cost | ◎ |
| In-house implementation | Full customization | Security risk, large effort | × |

**Decision:** AWS Cognito

**Reason:**
```typescript
// AWS Cognitoのカスタム属性で8種類のユーザー属性を管理
interface UserAttributes {
  'custom:user_type': 'lumber_mill' | 'market' | 'manufacturer' | 'precut' | 'builder' | 'wholesaler' | 'other';
  'custom:company_id': string;
  'custom:permissions': string; // "create_order,view_inventory,manage_users"
}

// 権限チェック関数
const hasPermission = (user: UserAttributes, permission: string): boolean => {
  const permissions = user['custom:permissions'].split(',');
  return permissions.includes(permission);
};
```

---

#### Axis 2: Frontend — React + TypeScript + Vite

**Requirements:**
- UI control per complex user attribute
- Large-volume data operations (inventory list, order history)
- Fast response

**Comparison of candidate technologies:**

| Technology | Pros | Cons | Verdict |
|------|---------|-----------|------|
| jQuery | Low learning cost | Hard to manage complex UI | × |
| Vue.js | Simple, easy to learn | Unsuited to large-scale apps | △ |
| React + TypeScript | Type-safe, ecosystem | Learning cost | ◎ |
| Next.js | SSR/SSG optimization | Over-spec (SPA-oriented) | △ |

**Decision:** React + TypeScript + Vite

**Reason:**
```typescript
// TypeScriptによる型安全なユーザー属性管理
type UserType = 'lumber_mill' | 'market' | 'manufacturer';

interface User {
  id: string;
  userType: UserType;
  companyId: string;
  permissions: Set<Permission>;
}

// ユーザー属性ごとの条件付きレンダリング
const Dashboard: React.FC<{ user: User }> = ({ user }) => {
  return (
    <div>
      {user.permissions.has('create_order') && <CreateOrderButton />}
      {user.permissions.has('view_inventory') && <InventoryList />}
      {user.userType === 'market' && <MarketPriceChart />}
    </div>
  );
};
```

**Introducing Tanstack Query (React Query):**
```typescript
import { useQuery } from '@tanstack/react-query';

const useInventory = (companyId: string) => {
  return useQuery({
    queryKey: ['inventory', companyId],
    queryFn: () => fetchInventory(companyId),
    staleTime: 5 * 60 * 1000, // 5分間キャッシュ
    refetchOnWindowFocus: true,
  });
};

// コンポーネントでの使用
const InventoryList: React.FC = () => {
  const { data, isLoading, error } = useInventory(user.companyId);

  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;

  return <Table data={data} />;
};
```

**Pros:**
- Efficient API caching (reduces wasteful requests)
- Separation of concerns for data fetching (separating components and logic)

---

#### Axis 3: Backend — Python (Flask + SQLAlchemy)

**Requirements:**
- Heavy PDF/Excel batch processing (parallel processing)
- Complex business logic (price calculation, inventory management)
- Turning existing Excel data into a DB

**Comparison of candidate technologies:**

| Technology | Pros | Cons | Verdict |
|------|---------|-----------|------|
| Node.js (Express) | TypeScript unification, lightweight | Unsuited to heavy processing | △ |
| Python (Flask) | Data processing, parallel processing | Performance (GIL) | ◎ |
| Golang (Gin) | Fast, concurrent processing | Small ecosystem | △ |
| Java (Spring Boot) | Enterprise track record | Heavy, learning cost | × |

**Decision:** Python (Flask + SQLAlchemy)

**Reason:**
```python
# 帳票生成：openpyxl で Excel を作り、LibreOffice ヘッドレスで PDF 化
import subprocess
from openpyxl import load_workbook

def generate_invoice(order) -> tuple[str, str]:
    """請求書を Excel テンプレートから生成し、PDF へ変換する。"""
    wb = load_workbook("templates/invoice.xlsx")
    ws = wb.active
    ws["B2"] = order.invoice_number
    ws["B3"] = order.customer_name
    # ... 明細を流し込む
    xlsx_path = f"invoices/{order.id}.xlsx"
    wb.save(xlsx_path)

    # 業務帳票はレイアウト再現性が重要なので、表計算をそのまま PDF 化する
    subprocess.run(
        ["libreoffice", "--headless", "--convert-to", "pdf", "--outdir", "invoices/", xlsx_path],
        check=True,
    )
    return xlsx_path, xlsx_path.replace(".xlsx", ".pdf")


# Excel→DB 取り込み：openpyxl（read_only）＋ psycopg2 の execute_values で一括 INSERT
from openpyxl import load_workbook
from psycopg2.extras import execute_values

def import_excel_to_db(conn, file_path: str) -> None:
    """既存 Excel を DB へ一括インポートする（S3 トリガーの Lambda で実行）。"""
    wb = load_workbook(file_path, read_only=True, data_only=True)
    rows = []
    for r in wb.active.iter_rows(min_row=2, values_only=True):
        product_id, name, price, stock = r
        if product_id is None or price is None:
            continue  # 欠損行はスキップ
        rows.append((product_id, name, float(price), stock))

    with conn.cursor() as cur:
        execute_values(
            cur,
            "INSERT INTO products (product_id, name, price, stock) VALUES %s",
            rows,  # 行ループの個別 INSERT ではなく一括投入（高速・低負荷）
        )
    conn.commit()
```

**Pros:**
- **openpyxl**: edit the template Excel as-is, faithfully reproducing the industry's document layouts
- **LibreOffice headless conversion**: generate a PDF identical to the Excel down to the millimeter (for business documents, a matching appearance matters)
- **Bulk INSERT via execute_values**: orders of magnitude faster than per-row ORM add; the import is separated into an S3-event-driven Lambda so it doesn't strain the server proper

---

#### Axis 4: Infrastructure — AWS (ECS on Fargate + Terraform)

**Requirements:**
- A scalable SaaS foundation
- Highly reproducible infrastructure (IaC)
- Container-based microservices

**Comparison of candidate technologies:**

| Technology | Pros | Cons | Verdict |
|------|---------|-----------|------|
| AWS EC2 | High freedom | Manual scaling, management cost | × |
| AWS ECS on Fargate | Serverless, auto-scale | Slightly higher cost | ◎ |
| Kubernetes (EKS) | Highest flexibility | Complex, over-spec | △ |
| Heroku | Easy | High cost, low flexibility | × |

**Decision:** AWS ECS on Fargate + Terraform

**Codifying infrastructure with Terraform:**
```hcl
# ECS Cluster
resource "aws_ecs_cluster" "main" {
  name = "lumber-saas-cluster"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }
}

# ECS Task Definition
resource "aws_ecs_task_definition" "app" {
  family                   = "lumber-saas-app"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = "1024"
  memory                   = "2048"

  container_definitions = jsonencode([{
    name  = "app"
    image = "${var.ecr_repository_url}:latest"
    portMappings = [{
      containerPort = 8080
      protocol      = "tcp"
    }]
    environment = [
      { name = "DATABASE_URL", value = var.database_url },
      { name = "COGNITO_USER_POOL_ID", value = aws_cognito_user_pool.main.id }
    ]
  }])
}

# Auto Scaling
resource "aws_appautoscaling_target" "ecs_target" {
  max_capacity       = 10
  min_capacity       = 2
  resource_id        = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
}

resource "aws_appautoscaling_policy" "ecs_policy" {
  name               = "scale-up"
  policy_type        = "TargetTrackingScaling"
  resource_id        = aws_appautoscaling_target.ecs_target.resource_id
  scalable_dimension = aws_appautoscaling_target.ecs_target.scalable_dimension
  service_namespace  = aws_appautoscaling_target.ecs_target.service_namespace

  target_tracking_scaling_policy_configuration {
    target_value = 70.0
    predefined_metric_specification {
      predefined_metric_type = "ECSServiceAverageCPUUtilization"
    }
  }
}
```

**Pros:**
- **Fargate**: no server management, auto-scale
- **Terraform**: codify the entire infrastructure, 100% reproducibility
- **Auto Scaling**: auto-scale according to CPU utilization (cost optimization)

---

## Non-engineer readiness: thorough simplification of UI/UX

### The challenge: can on-site non-engineers use it?

In legacy-industry workplaces, there are many users with low IT literacy. You need to accommodate users who "can use Excel but can't use a web system."

### Implementation: intuitive UI design

**1. Excel-like operability**
```typescript
// ag-Grid を使用したExcelライクなテーブル
import { AgGridReact } from 'ag-grid-react';

const InventoryTable: React.FC = () => {
  const columnDefs = [
    { field: 'product_name', headerName: '商品名', editable: true },
    { field: 'stock', headerName: '在庫数', editable: true },
    { field: 'price', headerName: '単価', editable: true },
  ];

  const onCellValueChanged = (params: any) => {
    // セル編集時、自動保存
    updateInventory(params.data);
  };

  return (
    <AgGridReact
      columnDefs={columnDefs}
      rowData={inventoryData}
      onCellValueChanged={onCellValueChanged}
      enableRangeSelection={true} // Excel風の範囲選択
    />
  );
};
```

**2. Clear error messages**
```typescript
// ❌ 悪い例
throw new Error('ValidationError: field "price" must be positive');

// ✅ 良い例
throw new Error('価格は0円以上で入力してください。現在の入力値: -1000円');
```

---

## Phased rollout: coexistence with existing operations

### The challenge: "a sudden full migration" fails

In legacy industries, "suddenly abolishing Excel and migrating to a web system" confuses the field and fails.

### Strategy: 3-stage rollout

```
Phase 1 (1–3 months): Information sharing only (Excel allowed alongside)
  ↓
Phase 2 (4–6 months): Partial migration of the ordering feature (Excel alongside)
  ↓
Phase 3 (7–12 months): Full migration (Excel retired)
```

**Implementation example:**
```python
# Phase 1: Excel アップロード機能
@app.route('/api/inventory/upload', methods=['POST'])
def upload_excel():
    """既存Excelをアップロードして、DBに同期"""
    file = request.files['file']
    df = pd.read_excel(file)

    # DB同期
    sync_inventory_from_dataframe(df)

    return jsonify({'message': 'Excelをアップロードしました。Web画面でも確認できます。'})

# Phase 2: Excelダウンロード機能（Excel併用）
@app.route('/api/inventory/download', methods=['GET'])
def download_excel():
    """Web上のデータをExcelでダウンロード"""
    inventory = Inventory.query.all()
    df = pd.DataFrame([inv.to_dict() for inv in inventory])

    output = io.BytesIO()
    df.to_excel(output, index=False)
    output.seek(0)

    return send_file(output, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                     as_attachment=True, download_name='inventory.xlsx')
```

---

## Summary: the 5 principles of legacy-industry-DX technology selection

1. **Prioritize industry fit above all** — choose technology that can handle the industry-specific complexity, not a generic solution
2. **Be thorough about non-engineer readiness** — design an intuitive UI/UX that even users with low IT literacy can use
3. **Plan a phased rollout** — don't migrate all at once; migrate gradually while coexisting with existing operations
4. **Secure long-term maintainability** — a design that endures 10+ years of long-term operation without breeding technical debt
5. **Guarantee reproducibility with IaC** — codify infrastructure to handle disaster recovery and scale-out

---

## Your legacy-industry DX is achievable too

Manufacturing, construction, logistics, agriculture/forestry/fisheries — we support the DX of every legacy industry. From requirements definition through design, implementation, and infrastructure construction, we can handle it one-stop.

We offer a **free technical consultation (30 minutes)**, so feel free to get in touch.

[Contact us here](/contact)
