Skip to main content
友田 陽大
B2B SaaS & DX strategy
レガシー産業DX
技術選定
業界DX
デジタル変革
TypeScript
Python
Golang
AWS

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
Reading time
9 min read
Author
友田 陽大
Share

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 axisImportanceDescription
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:

TechnologyProsConsVerdict
Firebase AuthEasy, large free tierLow flexibility of custom attributes×
Auth0High flexibilityHigh cost (MAU billing)
AWS CognitoAWS integration, custom attributesLearning cost
In-house implementationFull customizationSecurity risk, large effort×

Decision: AWS Cognito

Reason:

// 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:

TechnologyProsConsVerdict
jQueryLow learning costHard to manage complex UI×
Vue.jsSimple, easy to learnUnsuited to large-scale apps
React + TypeScriptType-safe, ecosystemLearning cost
Next.jsSSR/SSG optimizationOver-spec (SPA-oriented)

Decision: React + TypeScript + Vite

Reason:

// 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):

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:

TechnologyProsConsVerdict
Node.js (Express)TypeScript unification, lightweightUnsuited to heavy processing
Python (Flask)Data processing, parallel processingPerformance (GIL)
Golang (Gin)Fast, concurrent processingSmall ecosystem
Java (Spring Boot)Enterprise track recordHeavy, learning cost×

Decision: Python (Flask + SQLAlchemy)

Reason:

# 帳票生成: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:

TechnologyProsConsVerdict
AWS EC2High freedomManual scaling, management cost×
AWS ECS on FargateServerless, auto-scaleSlightly higher cost
Kubernetes (EKS)Highest flexibilityComplex, over-spec
HerokuEasyHigh cost, low flexibility×

Decision: AWS ECS on Fargate + Terraform

Codifying infrastructure with Terraform:

# 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

// 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

// ❌ 悪い例
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:

# 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

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

Got a challenge?

From design to implementation and operations — solo × generative AI

Implementation like this article's, end to end from requirements to production. Start with a free 30-minute technical consult and tell me about your situation.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading