Skip to content

Latest commit

ย 

History

History
443 lines (356 loc) ยท 18.4 KB

File metadata and controls

443 lines (356 loc) ยท 18.4 KB

StationAPI ใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ

ๆœ€็ต‚ๆ›ดๆ–ฐ: 2026ๅนด1ๆœˆ

็›ฎๆฌก


ๆฆ‚่ฆ

StationAPI ใฏๆ—ฅๆœฌใฎ้‰„้“้ง…ๆƒ…ๅ ฑใ‚’ๆไพ›ใ™ใ‚‹ gRPC API ใงใ™ใ€‚ใ‚ฏใƒชใƒผใƒณใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃใซๅŸบใฅใ„ใŸ4ๅฑคๆง‹้€ ใ‚’ๆŽก็”จใ—ใ€ใƒ“ใ‚ธใƒใ‚นใƒญใ‚ธใƒƒใ‚ฏใจๆŠ€่ก“็š„้–ขๅฟƒไบ‹ใ‚’ๆ˜Ž็ขบใซๅˆ†้›ขใ—ใฆใ„ใพใ™ใ€‚

ๆŠ€่ก“ใ‚นใ‚ฟใƒƒใ‚ฏ

้ …็›ฎ ๆŠ€่ก“
่จ€่ชž Rust (Edition 2021)
ใƒฉใƒณใ‚ฟใ‚คใƒ  tokio
ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น PostgreSQL 15+
ORM sqlx (ใ‚ณใƒณใƒ‘ใ‚คใƒซๆ™‚ใ‚ฏใ‚จใƒชๆคœ่จผ)
API gRPC (tonic)
ใ‚ทใƒชใ‚ขใƒฉใ‚คใ‚บ Protocol Buffers

ใƒฌใ‚คใƒคใƒผๆง‹้€ 

StationAPI ใฏ4ใคใฎๅฑคใงๆง‹ๆˆใ•ใ‚Œใฆใ„ใพใ™ใ€‚ๅ„ๅฑคใฏไพๅญ˜ๆ€งใฎๆ–นๅ‘ใŒๅ†…ๅด๏ผˆDomain๏ผ‰ใซๅ‘ใ‹ใ†ใ‚ˆใ†่จญ่จˆใ•ใ‚Œใฆใ„ใพใ™ใ€‚

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Presentation ๅฑค                       โ”‚
โ”‚            (gRPC Controller, ใ‚จใƒฉใƒผใƒใƒณใƒ‰ใƒชใƒณใ‚ฐ)           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      UseCase ๅฑค                          โ”‚
โ”‚           (Interactor, DTO, ใƒ“ใ‚ธใƒใ‚นใƒญใ‚ธใƒƒใ‚ฏ)              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   Infrastructure ๅฑค                      โ”‚
โ”‚        (RepositoryๅฎŸ่ฃ…, Rowๆง‹้€ ไฝ“, DBๆŽฅ็ถš)                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ†“
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      Domain ๅฑค                           โ”‚
โ”‚       (Entity, Repository Interface, ใƒ“ใ‚ธใƒใ‚นใƒซใƒผใƒซ)       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Domain ๅฑค (src/domain/)

่ฒฌๅ‹™: ใ‚ณใ‚ขใƒ“ใ‚ธใƒใ‚นใƒญใ‚ธใƒƒใ‚ฏใจใƒ‡ใƒผใ‚ฟใƒขใƒ‡ใƒซใฎๅฎš็พฉ

ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช/ใƒ•ใ‚กใ‚คใƒซ ๅ†…ๅฎน
entity/ ใƒ‰ใƒกใ‚คใƒณใ‚จใƒณใƒ†ใ‚ฃใƒ†ใ‚ฃ๏ผˆStation, Line, TrainType, Company ใชใฉ๏ผ‰
repository/ ใƒชใƒใ‚ธใƒˆใƒชใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚น๏ผˆasync_trait ใ‚’ไฝฟ็”จ๏ผ‰
normalize.rs ใƒ†ใ‚ญใ‚นใƒˆๆญฃ่ฆๅŒ–๏ผˆใฒใ‚‰ใŒใชโ†”ใ‚ซใ‚ฟใ‚ซใƒŠใ€ๅ…จ่ง’โ†”ๅŠ่ง’ๅค‰ๆ›๏ผ‰
error.rs ใƒ‰ใƒกใ‚คใƒณใ‚จใƒฉใƒผๅž‹๏ผˆNotFound, InfrastructureError, Unexpected๏ผ‰

่จญ่จˆๅŽŸๅ‰‡:

  • ๅค–้ƒจไพๅญ˜ใ‚’ๆŒใŸใชใ„็ด”็ฒ‹ใช Rust ใ‚ณใƒผใƒ‰
  • ใƒชใƒใ‚ธใƒˆใƒชใฏ trait ใจใ—ใฆๅฎš็พฉใ—ใ€ๅฎŸ่ฃ…ใ‚’ Infrastructure ๅฑคใซๅง”่ญฒ
  • ๅคš่จ€่ชžๅฏพๅฟœ๏ผˆๆ—ฅๆœฌ่ชžใ€ใ‚ซใ‚ฟใ‚ซใƒŠใ€ใƒญใƒผใƒžๅญ—ใ€ไธญๅ›ฝ่ชžใ€้Ÿ“ๅ›ฝ่ชž๏ผ‰

UseCase ๅฑค (src/use_case/)

่ฒฌๅ‹™: ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใƒ“ใ‚ธใƒใ‚นใƒญใ‚ธใƒƒใ‚ฏใจใƒ‡ใƒผใ‚ฟๅค‰ๆ›

ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒช/ใƒ•ใ‚กใ‚คใƒซ ๅ†…ๅฎน
interactor/query.rs QueryInteractor - ไธป่ฆใชใƒฆใƒผใ‚นใ‚ฑใƒผใ‚นๅฎŸ่ฃ…๏ผˆ็ด„950่กŒ๏ผ‰
traits/query.rs QueryUseCase ใƒˆใƒฌใ‚คใƒˆๅฎš็พฉ๏ผˆ20ไปฅไธŠใฎ้žๅŒๆœŸใƒกใ‚ฝใƒƒใƒ‰๏ผ‰
dto/ ใƒ‡ใƒผใ‚ฟๅค‰ๆ›ใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆ๏ผˆEntity โ†” gRPC ใƒกใƒƒใ‚ปใƒผใ‚ธ๏ผ‰
error.rs ใƒฆใƒผใ‚นใ‚ฑใƒผใ‚นใ‚จใƒฉใƒผๅž‹

้‡่ฆใชใƒกใ‚ฝใƒƒใƒ‰:

// update_station_vec_with_attributes (query.rs:169-265)
// - ้ง…ใƒ‡ใƒผใ‚ฟใซใƒฉใ‚คใƒณใ€ไผš็คพใ€ๅˆ—่ปŠ็จฎๅˆฅใ‚’ไป˜ๅŠ 
// - N+1ๅ•้กŒใ‚’ๅ›ž้ฟใ™ใ‚‹ใƒใƒƒใƒใ‚ฏใ‚จใƒช่จญ่จˆ
async fn update_station_vec_with_attributes(
    &self,
    mut stations: Vec<Station>,
    line_group_id: Option<u32>,
) -> Result<Vec<Station>, UseCaseError>

Infrastructure ๅฑค (src/infrastructure/)

่ฒฌๅ‹™: ใƒ‡ใƒผใ‚ฟๆฐธ็ถšๅŒ–ใจๅค–้ƒจใ‚ทใ‚นใƒ†ใƒ ้€ฃๆบ

ใƒ•ใ‚กใ‚คใƒซ ๅ†…ๅฎน
station_repository.rs StationRow + MyStationRepository ๅฎŸ่ฃ…
line_repository.rs LineRow + MyLineRepository ๅฎŸ่ฃ…
train_type_repository.rs TrainTypeRow + MyTrainTypeRepository ๅฎŸ่ฃ…
company_repository.rs CompanyRow + MyCompanyRepository ๅฎŸ่ฃ…

่จญ่จˆใƒ‘ใ‚ฟใƒผใƒณ:

  • ๅ„ Repository ใฏ Arc<Pool<Postgres>> ใ‚’ใƒฉใƒƒใƒ—
  • Internal*Repository ๆง‹้€ ไฝ“ใซๅฎŸ้š›ใฎ SQL ๅฎŸ่กŒใ‚’ๅง”่ญฒ
  • #[derive(sqlx::FromRow)] ใซใ‚ˆใ‚‹ๅž‹ๅฎ‰ๅ…จใช Row ใƒžใƒƒใƒ”ใƒณใ‚ฐ

Presentation ๅฑค (src/presentation/)

่ฒฌๅ‹™: ๅค–้ƒจ API ใฎๅ…ฌ้–‹ใจใƒชใ‚ฏใ‚จใ‚นใƒˆ/ใƒฌใ‚นใƒใƒณใ‚นใƒใƒณใƒ‰ใƒชใƒณใ‚ฐ

ใƒ•ใ‚กใ‚คใƒซ ๅ†…ๅฎน
controller/grpc.rs MyApi - 14ใฎ gRPC ใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆๅฎŸ่ฃ…
error.rs PresentationalError ใจ tonic::Status ใธใฎๅค‰ๆ›

ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚น่จญ่จˆ

ใƒ†ใƒผใƒ–ใƒซๆง‹ๆˆ

ใ™ในใฆใฎใƒ†ใƒผใƒ–ใƒซใฏ UNLOGGED ใจใ—ใฆไฝœๆˆใ•ใ‚Œใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใ‚’ๅ„ชๅ…ˆใ—ใฆใ„ใพใ™ใ€‚

ใƒ†ใƒผใƒ–ใƒซ ไธปใ‚ญใƒผ ๆฆ‚่ฆ
companies company_cd ้‰„้“ไผš็คพๆƒ…ๅ ฑ
lines line_cd ่ทฏ็ทšๆƒ…ๅ ฑ
stations station_cd ้ง…ๆƒ…ๅ ฑ
types id ๅˆ—่ปŠ็จฎๅˆฅ
station_station_types id ้ง…ใจๅˆ—่ปŠ็จฎๅˆฅใฎ้–ข้€ฃ
line_aliases id ่ทฏ็ทšใ‚จใ‚คใƒชใ‚ขใ‚น
connections - ้ง…้–“ๆŽฅ็ถš
aliases - ๆคœ็ดข็”จใ‚จใ‚คใƒชใ‚ขใ‚น

ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นๆœ€้ฉๅŒ–

-- ไฝฟ็”จใ—ใฆใ„ใ‚‹ PostgreSQL ๆ‹กๅผต
CREATE EXTENSION IF NOT EXISTS pg_trgm;    -- ใƒˆใƒฉใ‚คใ‚ฐใƒฉใƒ ๆคœ็ดข
CREATE EXTENSION IF NOT EXISTS btree_gist; -- GiST ใ‚คใƒณใƒ‡ใƒƒใ‚ฏใ‚น

-- ไธป่ฆใ‚คใƒณใƒ‡ใƒƒใ‚ฏใ‚น
CREATE INDEX idx_stations_station_g_cd ON stations(station_g_cd);
CREATE INDEX idx_stations_line_cd ON stations(line_cd);
CREATE INDEX idx_performance_station_name_trgm ON stations
    USING gin(station_name gin_trgm_ops);  -- ใ‚ใ„ใพใ„ๆคœ็ดข็”จ

ใ‚นใ‚ญใƒผใƒžๆ›ดๆ–ฐๆ™‚ใฎๆณจๆ„็‚น

  1. ใƒžใ‚คใ‚ฐใƒฌใƒผใ‚ทใƒงใƒณ: data/create_table.sql ใ‚’ๆ›ดๆ–ฐ
  2. Row ๆง‹้€ ไฝ“: ๅฏพๅฟœใ™ใ‚‹ *Row ๆง‹้€ ไฝ“ใ‚’ Infrastructure ๅฑคใงๆ›ดๆ–ฐ
  3. Entity: ๅฟ…่ฆใซๅฟœใ˜ใฆ Domain ๅฑคใฎ Entity ใ‚’ๆ›ดๆ–ฐ
  4. ๅค‰ๆ›ใƒญใ‚ธใƒƒใ‚ฏ: impl From<XxxRow> for Xxx ใ‚’ๆ›ดๆ–ฐ
  5. DTO: gRPC ใƒกใƒƒใ‚ปใƒผใ‚ธใธใฎๅค‰ๆ›ใ‚’ use_case/dto/ ใงๆ›ดๆ–ฐ

gRPC/ใ‚นใ‚ญใƒผใƒž่จญ่จˆ

ใ‚ตใƒผใƒ“ใ‚นใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆ

stationapi.proto ใง14ใฎใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใ‚’ๅฎš็พฉ:

ใ‚ซใƒ†ใ‚ดใƒช ใƒกใ‚ฝใƒƒใƒ‰
้ง…ๆคœ็ดข GetStationById, GetStationByIdList, GetStationsByGroupId, GetStationsByCoordinates, GetStationsByLineId, GetStationsByName, GetStationsByLineGroupId
่ทฏ็ทšๆคœ็ดข GetLineById, GetLinesByIdList, GetLinesByName
็ตŒ่ทฏๆคœ็ดข GetRoutes, GetRoutesMinimal, GetConnectedRoutes
ๅˆ—่ปŠ็จฎๅˆฅ GetTrainTypesByStationId, GetRouteTypes

Proto ๆ›ดๆ–ฐๆ™‚ใฎๆณจๆ„็‚น

  1. ๅพŒๆ–นไบ’ๆ›ๆ€ง: ๆ–ฐใƒ•ใ‚ฃใƒผใƒซใƒ‰ใซใฏ optional ใ‚ญใƒผใƒฏใƒผใƒ‰ใ‚’ไฝฟ็”จ
  2. ใƒ“ใƒซใƒ‰่จญๅฎš: build.rs ใง serde ใƒˆใƒฌใ‚คใƒˆใ‚’่ฟฝๅŠ 
  3. DTO ๆ›ดๆ–ฐ: src/use_case/dto/*.rs ใฎใƒžใƒƒใƒ”ใƒณใ‚ฐใ‚’ๆ›ดๆ–ฐ
  4. ใƒ†ใ‚นใƒˆๆ›ดๆ–ฐ: ๆ–ฐใƒ•ใ‚ฃใƒผใƒซใƒ‰ใฎ็ตฑๅˆใƒ†ใ‚นใƒˆใ‚’่ฟฝๅŠ 
// ๅพŒๆ–นไบ’ๆ›ๆ€งใฎใ‚ใ‚‹่ฟฝๅŠ ไพ‹
message Station {
    // ๆ—ขๅญ˜ใƒ•ใ‚ฃใƒผใƒซใƒ‰...
    optional string new_field = 25;  // optional ใง่ฟฝๅŠ 
}

ๅ‘ฝๅ่ฆๅ‰‡

Row ๆง‹้€ ไฝ“ vs Entity ใฎๅŒบๅˆฅ

็จฎๅˆฅ ๅ ดๆ‰€ ็›ฎ็š„ ็‰นๅพด
Row infrastructure/*.rs DB่กŒใฎ็›ดๆŽฅใƒžใƒƒใƒ”ใƒณใ‚ฐ #[derive(sqlx::FromRow)]ใ€DBใ‚ซใƒฉใƒ ๅใจไธ€่‡ด
Entity domain/entity/*.rs ใƒ‰ใƒกใ‚คใƒณใƒขใƒ‡ใƒซ ใƒ“ใ‚ธใƒใ‚นใƒญใ‚ธใƒƒใ‚ฏใ€ใƒใ‚นใƒˆๆง‹้€ ใ€ๅคš่จ€่ชžๅฏพๅฟœ

Row ๆง‹้€ ไฝ“

// infrastructure/station_repository.rs
#[derive(sqlx::FromRow, Clone)]
pub struct StationRow {
    pub station_cd: i32,           // DBใ‚ซใƒฉใƒ ๅใจไธ€่‡ด
    pub station_g_cd: i32,
    pub station_name: String,
    pub line_cd: i32,
    // ... ็ด„19ใƒ•ใ‚ฃใƒผใƒซใƒ‰
}

็‰นๅพด:

  • ใƒ•ใ‚ฃใƒผใƒซใƒ‰ๅใฏ PostgreSQL ใ‚ซใƒฉใƒ ๅใจๅฎŒๅ…จไธ€่‡ด๏ผˆsnake_case๏ผ‰
  • ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใƒใ‚คใƒ†ใ‚ฃใƒ–ๅž‹ใ‚’ไฝฟ็”จ: i32, i64, f64, Option<T>, String
  • ใƒญใ‚ธใƒƒใ‚ฏใ‚’ๆŒใŸใชใ„็ด”็ฒ‹ใชใƒ‡ใƒผใ‚ฟใƒ›ใƒซใƒ€ใƒผ

Entity ๆง‹้€ ไฝ“

// domain/entity/station.rs
pub struct Station {
    pub station_cd: u32,           // ใƒ“ใ‚ธใƒใ‚นๅž‹๏ผˆ็ฌฆๅทใชใ—๏ผ‰
    pub station_g_cd: u32,
    pub station_name: String,
    pub line: Option<Box<Line>>,   // ใƒใ‚นใƒˆๆง‹้€ 
    pub lines: Vec<Line>,          // ใ‚ณใƒฌใ‚ฏใ‚ทใƒงใƒณ
    pub train_type: Option<Box<TrainType>>,
    pub station_numbers: Vec<StationNumber>,
    // ... ็ด„66ใƒ•ใ‚ฃใƒผใƒซใƒ‰
}

็‰นๅพด:

  • ใƒ“ใ‚ธใƒใ‚นใ‚ปใƒžใƒณใƒ†ใ‚ฃใ‚ฏใ‚นใ‚’ๅๆ˜ ใ—ใŸๅž‹๏ผˆไพ‹: StopCondition ๅˆ—ๆŒ™ๅž‹๏ผ‰
  • ใƒใ‚นใƒˆๆง‹้€ ใ‚’ๅซใ‚€๏ผˆOption<Box<Line>>, Vec<Line> ใชใฉ๏ผ‰
  • ๅคš่จ€่ชžๅใ‚’ใ‚ตใƒใƒผใƒˆ: station_name_r๏ผˆใƒญใƒผใƒžๅญ—๏ผ‰, station_name_zh๏ผˆไธญๅ›ฝ่ชž๏ผ‰, station_name_ko๏ผˆ้Ÿ“ๅ›ฝ่ชž๏ผ‰
  • Clone, Debug, Serialize, Deserialize, PartialEq ใ‚’ๅฎŸ่ฃ…

ๅค‰ๆ›ใƒ•ใƒญใƒผ

Database (PostgreSQL)
    โ†“
Row (sqlx::FromRow)      โ† ็›ดๆŽฅใƒžใƒƒใƒ”ใƒณใ‚ฐ: StationRow
    โ†“
Entity (From<Row>)       โ† ๅž‹ๅค‰ๆ›ใ€NoneๅˆๆœŸๅŒ–: Station
    โ†“
Enriched Entity          โ† UseCaseๅฑคใงใƒใ‚นใƒˆใƒ‡ใƒผใ‚ฟ่ฟฝๅŠ 
    โ†“
gRPC Message             โ† Protoๅค‰ๆ›: proto::Station
    โ†“
Network Response

ใ‚ญใƒฃใƒƒใ‚ทใƒฅๆˆฆ็•ฅ

็พๅœจใฎ่จญ่จˆ: ๆ˜Ž็คบ็š„ใ‚ญใƒฃใƒƒใ‚ทใƒฅใชใ—

StationAPI ใฏ็พๆ™‚็‚นใงๆ˜Ž็คบ็š„ใชใ‚คใƒณใƒกใƒขใƒชใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚’ๅฎŸ่ฃ…ใ—ใฆใ„ใพใ›ใ‚“ใ€‚ใใฎไปฃใ‚ใ‚Šใ€ไปฅไธ‹ใฎๆœ€้ฉๅŒ–ๆˆฆ็•ฅใ‚’ๆŽก็”จใ—ใฆใ„ใพใ™ใ€‚

ใƒใƒƒใƒใ‚ฏใ‚จใƒชใซใ‚ˆใ‚‹ๆš—้ป™็š„ใ‚ญใƒฃใƒƒใ‚ทใƒฅ

query.rs:169-265 ใฎ update_station_vec_with_attributes ใƒกใ‚ฝใƒƒใƒ‰ใงใฏใ€N+1ๅ•้กŒใ‚’ๅ›ž้ฟใ™ใ‚‹ใŸใ‚ใซใƒใƒƒใƒใ‚ฏใ‚จใƒชใ‚’ไฝฟ็”จใ—ใฆใ„ใพใ™ใ€‚

// 1. ใ™ในใฆใฎ station_g_cd ใ‚’ๆŠฝๅ‡บ
let station_group_ids = stations.iter()
    .map(|s| s.station_g_cd as u32)
    .collect::<Vec<u32>>();

// 2. ไธ€ๆ‹ฌใ‚ฏใ‚จใƒชใง้–ข้€ฃใƒ‡ใƒผใ‚ฟใ‚’ๅ–ๅพ—๏ผˆN+1ๅ›ž้ฟ๏ผ‰
let stations_by_group_ids = self
    .get_stations_by_group_id_vec(&station_group_ids).await?;
let lines = self
    .get_lines_by_station_group_id_vec(&station_group_ids).await?;
let train_types = self
    .get_train_types_by_station_id_vec(&station_ids, line_group_id).await?;

// 3. ใƒกใƒขใƒชไธŠใง้–ข้€ฃไป˜ใ‘๏ผˆO(1)ใ‚ฏใ‚จใƒช/ใ‚จใƒณใƒชใƒƒใƒใƒกใƒณใƒˆ๏ผ‰

็ตๆžœ: ใ‚จใƒณใƒชใƒƒใƒใƒกใƒณใƒˆๅ‡ฆ็†ใ‚ใŸใ‚ŠO(1)ใ‚ฏใ‚จใƒช๏ผˆN้ง…ใซๅฏพใ—ใฆNๅ›žใฎใ‚ฏใ‚จใƒชใงใฏใชใ„๏ผ‰

HashSet ใซใ‚ˆใ‚‹้‡่ค‡ๆŽ’้™ค

query.rs:223 ไป˜่ฟ‘ใงใ‚คใƒณใƒกใƒขใƒช้‡่ค‡ๆŽ’้™คใ‚’ๅฎŸๆ–ฝ:

let mut seen_line_cds = std::collections::HashSet::new();
let lines: Vec<Line> = lines
    .iter()
    .filter(|&l| {
        l.station_g_cd.unwrap_or(0) == station.station_g_cd
            && seen_line_cds.insert(l.line_cd)  // HashSetใง้‡่ค‡้˜ฒๆญข
    })
    .cloned()
    .collect();

ใ‚ญใƒฃใƒƒใ‚ทใƒฅใ‚’ๅฎŸ่ฃ…ใ—ใชใ„็†็”ฑ

  1. ใƒ‡ใƒผใ‚ฟ่ฆๆจก: ๆ—ฅๆœฌใฎ้‰„้“ใƒ‡ใƒผใ‚ฟใฏๆฏ”่ผƒ็š„ๅฐ่ฆๆจก๏ผˆ็ด„9,000้ง…๏ผ‰
  2. ๆ›ดๆ–ฐ้ ปๅบฆ: CSV ใ‚คใƒณใƒใƒผใƒˆใซใ‚ˆใ‚‹ใƒ‡ใƒผใ‚ฟๆ›ดๆ–ฐใŒๅ‰ๆ
  3. ใ‚นใƒ†ใƒผใƒˆใƒฌใ‚น่จญ่จˆ: ๅ„ใƒชใ‚ฏใ‚จใ‚นใƒˆใฏ็‹ฌ็ซ‹ใ—ใฆๅ‡ฆ็†
  4. PostgreSQL ใฎๆœ€้ฉๅŒ–: ใ‚คใƒณใƒ‡ใƒƒใ‚ฏใ‚นใจใ‚ฏใ‚จใƒชใƒ—ใƒฉใƒณใƒŠใƒผใซใ‚ˆใ‚‹ๅŠน็އๅŒ–

ๅฐ†ๆฅใฎๆคœ่จŽไบ‹้ …

ๅคง่ฆๆจกๅŒ–ใ‚„้ซ˜้ ปๅบฆใ‚ขใ‚ฏใ‚ปใ‚นใŒๅฟ…่ฆใชๅ ดๅˆ:

  • moka ใ‚„ lru ใ‚ฏใƒฌใƒผใƒˆใซใ‚ˆใ‚‹ๆœ‰็•Œใ‚คใƒณใƒกใƒขใƒชใ‚ญใƒฃใƒƒใ‚ทใƒฅ
  • CSV ใ‚คใƒณใƒใƒผใƒˆๆ™‚ใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅ็„กๅŠนๅŒ–
  • station_g_cd ๅ˜ไฝใฎใ‚ฟใ‚ฐใƒ™ใƒผใ‚น็„กๅŠนๅŒ–

ใƒ‡ใƒผใ‚ฟใƒ•ใƒญใƒผ

ๅ…ธๅž‹็š„ใชใƒชใ‚ฏใ‚จใ‚นใƒˆใƒ•ใƒญใƒผ

[Client]
    โ”‚
    โ–ผ gRPC Request
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Presentation ๅฑค (grpc.rs)                     โ”‚
โ”‚  โ””โ”€ MyApi::get_stations_by_id()              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚
    โ–ผ QueryUseCase ใƒกใ‚ฝใƒƒใƒ‰ๅ‘ผใณๅ‡บใ—
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ UseCase ๅฑค (query.rs)                         โ”‚
โ”‚  โ”œโ”€ QueryInteractor::get_station_by_id()     โ”‚
โ”‚  โ””โ”€ update_station_vec_with_attributes()     โ”‚
โ”‚      โ”œโ”€ ้ง…ใ‚ฐใƒซใƒผใƒ—ไธ€ๆ‹ฌๅ–ๅพ—                      โ”‚
โ”‚      โ”œโ”€ ่ทฏ็ทšไธ€ๆ‹ฌๅ–ๅพ—                           โ”‚
โ”‚      โ”œโ”€ ไผš็คพไธ€ๆ‹ฌๅ–ๅพ—                           โ”‚
โ”‚      โ””โ”€ ๅˆ—่ปŠ็จฎๅˆฅไธ€ๆ‹ฌๅ–ๅพ—                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚
    โ–ผ Repository ใƒกใ‚ฝใƒƒใƒ‰ๅ‘ผใณๅ‡บใ—
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Infrastructure ๅฑค (station_repository.rs)     โ”‚
โ”‚  โ””โ”€ MyStationRepository::find_by_id()        โ”‚
โ”‚      โ””โ”€ SQL ใ‚ฏใ‚จใƒชๅฎŸ่กŒ (sqlx)                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚
    โ–ผ Row โ†’ Entity ๅค‰ๆ›
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Domain ๅฑค (entity/station.rs)                 โ”‚
โ”‚  โ””โ”€ impl From<StationRow> for Station        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚
    โ–ผ Entity โ†’ gRPC Message ๅค‰ๆ›
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ UseCase ๅฑค (dto/station.rs)                   โ”‚
โ”‚  โ””โ”€ impl From<Station> for proto::Station    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
    โ”‚
    โ–ผ gRPC Response
[Client]

ใ‚จใƒฉใƒผไผๆ’ญใƒใ‚งใƒผใƒณ

DomainError (sqlx ใ‚จใƒฉใƒผ็ญ‰)
    โ†“ ?ๆผ”็ฎ—ๅญ
UseCaseError (ใƒฆใƒผใ‚นใ‚ฑใƒผใ‚นๅฑค)
    โ†“ From ใƒˆใƒฌใ‚คใƒˆ
PresentationalError (ใƒ—ใƒฌใ‚ผใƒณใƒ†ใƒผใ‚ทใƒงใƒณๅฑค)
    โ†“ Into ใƒˆใƒฌใ‚คใƒˆ
tonic::Status (gRPC ใƒฏใ‚คใƒคใƒผใƒ•ใ‚ฉใƒผใƒžใƒƒใƒˆ)

ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชๆง‹้€ 

stationapi/src/
โ”œโ”€โ”€ domain/                          # ใ‚ณใ‚ขใƒ“ใ‚ธใƒใ‚นใƒญใ‚ธใƒƒใ‚ฏ
โ”‚   โ”œโ”€โ”€ entity/                      # ใƒ‰ใƒกใ‚คใƒณใ‚จใƒณใƒ†ใ‚ฃใƒ†ใ‚ฃ
โ”‚   โ”‚   โ”œโ”€โ”€ station.rs               # Station (66ใƒ•ใ‚ฃใƒผใƒซใƒ‰)
โ”‚   โ”‚   โ”œโ”€โ”€ line.rs                  # Line (40ใƒ•ใ‚ฃใƒผใƒซใƒ‰)
โ”‚   โ”‚   โ”œโ”€โ”€ train_type.rs            # TrainType
โ”‚   โ”‚   โ”œโ”€โ”€ company.rs               # Company
โ”‚   โ”‚   โ”œโ”€โ”€ line_symbol.rs           # LineSymbol
โ”‚   โ”‚   โ””โ”€โ”€ station_number.rs        # StationNumber
โ”‚   โ”œโ”€โ”€ repository/                  # ๆŠฝ่ฑกใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใƒผใ‚น
โ”‚   โ”‚   โ”œโ”€โ”€ station_repository.rs
โ”‚   โ”‚   โ”œโ”€โ”€ line_repository.rs
โ”‚   โ”‚   โ”œโ”€โ”€ train_type_repository.rs
โ”‚   โ”‚   โ””โ”€โ”€ company_repository.rs
โ”‚   โ”œโ”€โ”€ normalize.rs                 # ใƒ†ใ‚ญใ‚นใƒˆๆญฃ่ฆๅŒ–
โ”‚   โ””โ”€โ”€ error.rs                     # DomainError
โ”‚
โ”œโ”€โ”€ use_case/                        # ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใƒญใ‚ธใƒƒใ‚ฏ
โ”‚   โ”œโ”€โ”€ interactor/
โ”‚   โ”‚   โ””โ”€โ”€ query.rs                 # QueryInteractor (็ด„950่กŒ)
โ”‚   โ”œโ”€โ”€ traits/
โ”‚   โ”‚   โ””โ”€โ”€ query.rs                 # QueryUseCase ใƒˆใƒฌใ‚คใƒˆ
โ”‚   โ”œโ”€โ”€ dto/                         # ใƒ‡ใƒผใ‚ฟๅค‰ๆ›
โ”‚   โ”‚   โ”œโ”€โ”€ station.rs
โ”‚   โ”‚   โ”œโ”€โ”€ line.rs
โ”‚   โ”‚   โ”œโ”€โ”€ train_type.rs
โ”‚   โ”‚   โ””โ”€โ”€ company.rs
โ”‚   โ””โ”€โ”€ error.rs                     # UseCaseError
โ”‚
โ”œโ”€โ”€ infrastructure/                  # ใƒ‡ใƒผใ‚ฟๆฐธ็ถšๅŒ–
โ”‚   โ”œโ”€โ”€ station_repository.rs        # StationRow + MyStationRepository
โ”‚   โ”œโ”€โ”€ line_repository.rs           # LineRow + MyLineRepository
โ”‚   โ”œโ”€โ”€ train_type_repository.rs     # TrainTypeRow + MyTrainTypeRepository
โ”‚   โ”œโ”€โ”€ company_repository.rs        # CompanyRow + MyCompanyRepository
โ”‚   โ””โ”€โ”€ error.rs                     # InfrastructureError
โ”‚
โ”œโ”€โ”€ presentation/                    # ๅค–้ƒจAPI
โ”‚   โ”œโ”€โ”€ controller/
โ”‚   โ”‚   โ””โ”€โ”€ grpc.rs                  # MyApi (14ใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆ)
โ”‚   โ””โ”€โ”€ error.rs                     # PresentationalError
โ”‚
โ”œโ”€โ”€ lib.rs                           # ใƒขใ‚ธใƒฅใƒผใƒซๅฎฃ่จ€
โ””โ”€โ”€ main.rs                          # ใ‚จใƒณใƒˆใƒชใƒผใƒใ‚คใƒณใƒˆ

้–ข้€ฃใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ