आर्किटेक्चर¶
सर्वर की बनावट: internal/ के नीचे hexagonal परतें, bounded context, उन्हें
जोड़ने वाले event, और वे static जाँचें जो कोड को इन नियमों के अनुसार बनाए रखती हैं। कोड
internal/domain, internal/app, internal/adapters/{driving,driven} में है
और cmd/evey/wire*.go में जोड़ा जाता है; बनावट ADR-0001
तय करता है, repository का बँटवारा ADR-0009 और event
bus ADR-0010। build करने और चलाने के लिए DEV.md
पढ़ें; किसी बदलाव के लिए कौन सी फ़ाइलें छूनी हैं, यह जानने के लिए
कोड का नक्शा पढ़ें।
बैकएंड Go में है; वेब, मोबाइल और टीवी क्लाइंट frontend/ के नीचे एक TypeScript/React
monorepo हैं (मोबाइल और टीवी के लिए React Native / Expo)।
परतें¶
flowchart LR
driving[Driving adapter<br/>internal/adapters/driving<br/>grpcapi, http, pluginhost]
app[ऐप्लिकेशन सर्विस<br/>internal/app/context]
domain[Domain<br/>internal/domain/context<br/>model, value object, port]
driven[Driven adapter<br/>internal/adapters/driven<br/>postgres, ffmpeg, mkvgo, ...]
driving --> app --> domain
driven -. port लागू करते हैं .-> domain
| फ़ोल्डर | क्या रखता है | नियम |
|---|---|---|
internal/domain/<context>/ |
model, value object और port (interface) | किसी framework का import नहीं, किसी दूसरे context का import नहीं |
internal/app/<context>/ |
ऐप्लिकेशन सर्विस जो port के ज़रिए काम का समन्वय करती हैं | IO एक port से होकर जाता है; dlna (SSDP / UPnP AVTransport) और loadtest (उसका अपना HTTP client) दो जानबूझकर रखे गए अपवाद हैं जो सीधे नेटवर्क तक जाते हैं |
internal/adapters/driven/ |
बाहर जाने वाले adapter जो port लागू करते हैं: postgres, meilisearch, ffmpeg, mkvgo, filesystem, prober, opensubtitles, email, webpush, webhooksender, pluginrunner, metadata, introproviders, search |
तीन और domain port की जगह app परत के किसी interface को पूरा करते हैं: itunes और musicbrainz (app/music.AlbumArtFetcher), ociregistry (app/updatecheck) |
internal/adapters/driving/ |
अंदर आने वाले adapter: grpcapi (+ grpc-gateway HTTP), http (static, auth middleware, SSE, HLS, प्लगइन iframe की फ़ाइलें), pluginhost (Unix socket पर gRPC सर्वर, peer credentials से पहचान, वह होस्ट API जिसे सैंडबॉक्स वाले प्लगइन वापस कॉल करते हैं) |
|
internal/pkg/ |
साझा तकनीकी केंद्र: auth, logger, eventbus, i18n, metrics, tracing, ratelimit, procfork, ssrf, cache, ... | |
internal/config/ |
सर्वर का कॉन्फ़िगरेशन | |
gen/ |
बनाया गया proto कोड |
निर्भरता का नियम: adapters -> app -> domain। domain कभी app या adapter को import
नहीं करता। जोड़ने का काम हाथ से लिखा गया है (google/wire नहीं), cmd/evey/wire.go
में और उसके साथ की फ़ाइलों wire_infra.go, wire_services.go, wire_adapters.go,
wire_events.go, wire_helpers.go में। टेस्ट adapter को छोड़कर fake implementation डालते
हैं जो वही domain port लागू करते हैं: 21 में से 20 domain package के साथ एक fakes/
sub-package आता है।
Bounded context¶
बत्तीस context package internal/domain/ और internal/app/ के नीचे हैं: 19
दोनों परतों में हैं, 2 सिर्फ़ domain में (ratings, shared), 11 सिर्फ़ app में। ये
कुछ हद तक एक साझा bus पर domain event के ज़रिए साथ काम करते हैं; app परत पड़ोसी
context का domain package सीधे भी पढ़ती है (देखें संबंध)।
| Context | ज़िम्मेदारी |
|---|---|
user |
खाते, पहचान, सेशन, पसंद |
library |
लाइब्रेरी, फ़ोल्डर, स्कैन के काम, मीडिया इंडेक्स, लोग, संग्रह, किसी प्रोफ़ाइल के पसंदीदा और thumbs (thumbs की कोई app सर्विस नहीं: रेटिंग वाला RPC UserRatingRepository को जैसा है वैसा सहेजता और पढ़ता है), trickplay, इंट्रो/क्रेडिट की पहचान |
media (सिर्फ़ app) |
शेयर लिंक और stream token |
mediarequest |
उपयोगकर्ताओं के भेजे मीडिया अनुरोध |
metadata |
TMDB / OMDb से जानकारी जोड़ना, artwork |
playback |
stream, प्रगति, रिमोट कंट्रोल, अभी क्या चल रहा है |
transcoding |
ffmpeg के काम, HLS segment |
subtitle |
अंदर के + बाहरी + opensubtitles |
mkv |
MKV container के काम (mkvgo के ज़रिए) |
music |
ऑडियो के लिए ख़ास प्लेबैक + tagging |
photo |
फ़ोटो लाइब्रेरी |
favorite (सिर्फ़ app) |
हर प्रोफ़ाइल के पसंदीदा; model और FavoriteRepository domain/library में रहते हैं |
playlist |
उपयोगकर्ता की प्लेलिस्ट, स्मार्ट प्लेलिस्ट |
collection (सिर्फ़ app) |
एडमिन के चुने संग्रह; इसके port domain/library में रहते हैं |
ratings (सिर्फ़ domain) |
देशों के वर्गीकरण (CSA, FSK, MPAA, BBFC, ACB) को न्यूनतम उम्र की एक धुरी पर लाता है |
feed |
गतिविधि feed / खोजें |
activity |
उपयोगकर्ता की गतिविधि की समय-रेखा |
chromecast |
कास्ट सेशन का जीवन-चक्र |
syncplay |
कई डिवाइस वाले sync सेशन |
plugin |
प्लगइन रजिस्टर, सैंडबॉक्स token, जीवन-चक्र |
notification |
ऐप के अंदर + webpush सूचनाएँ |
webhook |
बाहर जाने वाले वेबहुक |
backup |
DB + कॉन्फ़िगरेशन का बैकअप/रीस्टोर |
config (सिर्फ़ app) |
सहेजी गई सर्वर सेटिंग्स, जिन्हें REST और gRPC दोनों रास्ते लिखते हैं |
dlna (सिर्फ़ app) |
SSDP से खोज और UPnP AVTransport नियंत्रण |
loadtest (सिर्फ़ app) |
सर्वर के अपने HTTP स्टैक पर दर्शकों की नकल करता है: HLS, या कच्ची फ़ाइल, जब क्लाइंट का फ़ैसला direct play हो |
maintenance (सिर्फ़ app) |
एडमिन के शुरू किए मीडिया रखरखाव: सीक इंडेक्स फिर से बनाना, मरम्मत का जर्नल, वापस लेना, ऑडियो की अखंडता |
presence (सिर्फ़ app) |
मेमोरी में रहने वाला, न सहेजा जाने वाला रजिस्टर कि अभी कौन जुड़ा है |
qoe (सिर्फ़ app) |
CMCD (CTA-5004) प्लेबैक telemetry लेना और जोड़ना |
demo (सिर्फ़ app) |
सार्वजनिक डेमो सर्वर (EVEY_DEMO=1): अपने खाते, लाइब्रेरी और मुफ़्त लाइसेंस वाला मीडिया प्रोडक्ट की अपनी सर्विस से भरता है, और खुद को रीसेट करता है |
updatecheck (सिर्फ़ app) |
चल रहे build की तुलना उससे करता है जो registry प्रकाशित करती है, और एडमिन को बताता है; यह कभी कुछ अपडेट नहीं करता |
shared (सिर्फ़ domain) |
सब पर लागू port (EventBus, TransactionalUnit, ServerConfigRepository, ShareLinkRepository), typed id, cursor, stream token |
संबंध¶
| भेजने वाला | Event | कौन लेता है |
|---|---|---|
library |
LibraryCreated / LibraryDeleted, LibraryScanStarted / LibraryScanCompleted, MediaItemAdded, MediaItemRemoved, TrickplayGenerated |
media.item.added: notification, metadata (अपने-आप जानकारी जोड़ना), library खुद (trickplay worker + इंट्रो की पहचान), feed (genre cache अमान्य करना) |
playback |
PlaybackStarted / PlaybackStopped |
आज कोई अलग subscriber नहीं; सिर्फ़ webhook का "*" catch-all subscriber इन्हें पाता है |
user |
UserCreated, user.password.reset_requested |
पहले का कोई subscriber नहीं; दूसरे को email driven adapter लेता है |
pluginrunner |
plugin.error / plugin.started |
app/plugin अपने सुरक्षा डैशबोर्ड के लिए, notification एडमिन के लिए |
shared |
ServerConfigChanged |
वे सर्विस जो एडमिन पैनल में सहेजी गई सेटिंग बिना restart के लागू करती हैं (SMTP, समय-क्षेत्र, CPU बजट, ...) |
- खोज का इंडेक्स बनाना event से नहीं चलता:
library.ScanServiceसीधेlibrary.SearchIndexport को कॉल करता है। - domain event को प्लगइन तक ले जाने वाली कोई व्यवस्था (bridge) नहीं है: प्लगइन सीमित दायरे वाले JWT से API तक पहुँचते हैं (ADR-0003)। bus खुद ADR-0010 है।
shared.TransactionalUnitकई aggregate वाले लेखन को एक transaction में रखता है (ADR-0002)।
internal/domain के नीचे का कोई package किसी दूसरे context का domain package import
नहीं करता; यह नियम internal/contractguard/domain_imports_test.go लागू करता है
(देखें आर्किटेक्चर की जाँचें)। app परत इससे बँधी नहीं है: आज
15 app package किसी पड़ोसी context का domain package import करते हैं।
आम डेटा का रास्ता - "उपयोगकर्ता एक मीडिया चलाता है"¶
- मोबाइल/वेब क्लाइंट
POST /api/v1/playback/startकॉल करता है (grpc-gateway route)। internal/adapters/driving/httpअपना auth middleware चलाता है (auth_middleware.go,httpAuthMiddleware), सेशन निकालता है।internal/adapters/driving/grpcapi(playback_handler.go) proto जाँचता है,app/playback.CommandService.StartStreamकॉल करता है (command/query का बँटवारा, देखें ADR-0009)।app/playback.CommandServicedomain port कॉल करता है:user.UserRepository,playback.SessionQueryRepo(idempotency + डिवाइस की सीमा),playback.SessionCommandRepo, फिरshared.EventBus.Publish(PlaybackStarted)।- driven adapter काम करते हैं:
postgresसेशन को एकshared.TransactionalUnit.Doदायरे के अंदर सहेजता है; प्रोसेस के अंदर का bus (internal/pkg/eventbus) asynchronous रूप से आगे भेजता है। PlaybackStartedका आज कोई अलग subscriber नहीं: सिर्फ़webhookका"*"catch-all subscriber इसे पाता है।- जवाब grpc-gateway से JSON के रूप में वापस जाता है; प्रगति के अपडेट SSE / gRPC stream पर आते हैं।
प्लेयर के manifest और segment माँगने के बाद क्या होता है, यह प्लेबैक पाइपलाइन का विषय है।
आर्किटेक्चर की जाँचें¶
ऊपर के नियम सिर्फ़ दस्तावेज़ में नहीं हैं। internal/contractguard/ में 30 फ़ाइलों में
58 static टेस्ट हैं, जो कोड के इनसे हटने पर build फ़ेल कर देते हैं।
hexagonal नियम खुद TestDomainContextsDoNotImportEachOther (ADR-0001) है,
domain_imports_test.go में। यह internal/domain की सारी फ़ाइलें जाँचता है और ठीक यही लागू करता
है: एक context internal/domain/shared और अपना package tree import कर सकता है,
internal/domain से और कुछ नहीं, और internal/app या internal/adapters कभी नहीं।
इसका दायरा internal/domain पर रुक जाता है: यह app परत के बारे में कुछ नहीं कहता।
बाकी जाँचें उन अनुबंधों को सुरक्षित रखती हैं जो किसी भाषा या प्रोसेस की सीमा पार करते हैं, उनमें से कुछ:
| जाँच | यह क्या मना करती है |
|---|---|
TestNoClientReadOfAFieldTheServerNeverWrites |
proto जवाब का ऐसा फ़ील्ड जिसे कोई क्लाइंट पढ़ता है पर कोई सर्वर कोड नहीं लिखता |
TestRegisteredHandlersGetEveryPortTheyDeclare, TestBootInstallsItsSilentWirings |
ऐसी जोड़ जो वरना चुपचाप फ़ेल हो जाती |
TestPostgresRepositoriesBoundTheirQueries |
timeout के बिना Postgres repository की query |
TestProcfork_EveryForkGoesThroughCommand |
ऐसा subprocess fork जो internal/pkg/procfork को छोड़कर जाता है |
TestEverySortFieldTheAPIAcceptsHasASQLColumn, ...CanBuildACursorValue |
ऐसा sort फ़ील्ड जो SQL कॉलम map या cursor encoder में नहीं है |
TestEveryWebSurfaceReachingAnAdminRPCIsClassified, और मोबाइल और टीवी के लिए उसका native रूप |
इंटरफ़ेस का ऐसा हिस्सा जो सिर्फ़-एडमिन RPC कॉल करता है, बिना तय किए कि उसे कौन देखता है |
TestEveryAllowedSlotIsMountedOnWebAndMobile, TestPluginBridgeParityWebMobile |
प्लगइन का ऐसा स्लॉट या bridge संदेश जो सिर्फ़ एक क्लाइंट पर है |
TestImageSubtitleCodecsAgreeAcrossServerAndClients |
किसी प्लेयर पर bitmap सबटाइटल codec की एक दूसरी सूची |
TestHandWrittenClientTypesMatchTheEndpointTheyCall |
मोबाइल/टीवी का हाथ से लिखा जवाब टाइप जो अपने endpoint से अलग हो जाए |
TestOnlyThePlanDecidesPlayback, TestNoCallerDerivesDownstreamOfThePlaybackPlan |
योजना तय करने वाले के बाहर लिया गया प्लेबैक का फ़ैसला (ADR-0030) |
TestRestartExitCodeAgreesWithTheEntrypoint |
Go का exit code और docker/entrypoint.sh restart के मतलब पर असहमत हों |
TestServerEmittedI18nKeysExistInCatalogue, TestI18nPlaceholdersAgreeAcrossLocales |
सर्वर से निकली ऐसी कुंजी जो अनुवाद सूची में नहीं, भाषाओं के बीच अलग placeholder |
C4 आरेख¶
चार आरेख Evey को घटते zoom स्तरों पर दिखाते हैं, C4 model के
हिसाब से। node के नाम internal/ के नीचे के असली Go package नाम हैं। container के
नाम evey-server, evey-postgres और evey-caddy के लिए docker-compose.yml से आते
हैं; evey-web, evey-meilisearch, evey-mobile और evey-mailcatcher
docker-compose.override.yml में बताए गए हैं और सिर्फ़ डेवलपमेंट वाली बनावट में होते
हैं।
स्तर 1 - सिस्टम का संदर्भ¶
Evey से कौन जुड़ता है, और Evey किन तीसरे पक्षों तक पहुँचता है।
flowchart LR
user[उपयोगकर्ता<br/>वेब / मोबाइल / टीवी क्लाइंट]
admin[एडमिन<br/>वेब /admin इंटरफ़ेस]
oidc[OIDC पहचान provider<br/>वैकल्पिक SSO]
tmdb[TMDB API<br/>फ़िल्म/सीरीज़ मेटाडेटा]
omdb[OMDb API<br/>बैकअप मेटाडेटा + रेटिंग]
imdb[IMDbAPI / TVmaze<br/>बैकअप सीरीज़ मेटाडेटा]
art[MusicBrainz / iTunes Search<br/>एल्बम artwork]
introdb[IntroDB / TIDB<br/>इंट्रो + क्रेडिट के समय]
opensubs[OpenSubtitles<br/>सबटाइटल खोज]
webpush[Web Push gateway<br/>FCM / APNs / VAPID]
registry[OCI registry<br/>प्रकाशित इमेज tag]
subgraph evey[Evey मीडिया सर्वर]
core((self-hosted मीडिया प्लेटफ़ॉर्म<br/>लाइब्रेरी, प्लेबैक, ट्रांसकोडिंग,<br/>संगीत, फ़ोटो, प्लगइन))
end
user -->|HTTPS<br/>HLS / SSE / gRPC-Web| evey
admin -->|HTTPS<br/>/admin| evey
evey -.->|OIDC code flow| oidc
evey -.->|HTTPS मेटाडेटा लाना| tmdb
evey -.->|HTTPS मेटाडेटा लाना| omdb
evey -.->|HTTPS मेटाडेटा लाना| imdb
evey -.->|HTTPS artwork लाना| art
evey -.->|HTTPS इंट्रो खोजना| introdb
evey -.->|HTTPS सबटाइटल लाना| opensubs
evey -.->|HTTPS push भेजना| webpush
evey -.->|HTTPS अपडेट की जाँच| registry
स्तर 2 - Container¶
docker compose --env-file .env.dev up से चालू होने वाली डिप्लॉय की जा सकने वाली
इकाइयाँ। ठोस तीर चलते समय का ट्रैफ़िक हैं; बिंदीदार तीर तीसरे पक्षों की ओर बाहर जाते
हैं।
flowchart TB
user([उपयोगकर्ता का ब्राउज़र / मोबाइल])
admin([एडमिन का ब्राउज़र])
subgraph compose[Docker Compose नेटवर्क]
caddy[evey-caddy<br/>Caddy 2.9<br/>:80 / :443<br/>TLS, रिवर्स प्रॉक्सी]
web[evey-web<br/>Vite dev / nginx prod<br/>:5173 dev<br/>React SPA + RN web]
server[evey-server<br/>Go binary<br/>:9091 HTTP+gRPC-gateway<br/>अंदरूनी :9090 gRPC]
pg[(evey-postgres<br/>PostgreSQL 16<br/>:5432)]
meili[(evey-meilisearch<br/>Meilisearch 1.12<br/>:7700)]
mobile[evey-mobile<br/>सिर्फ़ dev<br/>:8081 Expo / Metro bundler]
mail[evey-mailcatcher<br/>सिर्फ़ dev<br/>:1025 SMTP / :1080 UI]
end
tmdb[[TMDB / OMDb / OpenSubtitles]]
fs[(/media volume<br/>सिर्फ़-पढ़ने वाले bind mount)]
cache[(evey-cache volume<br/>transcode + trickplay)]
data[(evey-data volume<br/>कुंजियाँ, लॉग, बैकअप,<br/>प्लगइन, avatar)]
user -->|HTTPS| caddy
admin -->|HTTPS /admin| caddy
caddy -->|HTTP /| web
caddy -->|HTTP /v1, /api, SSE, HLS| server
web -->|/v1 से gRPC-Web| server
user -.->|Metro dev bundle| mobile
server -->|SQL/TCP 5432| pg
server -->|HTTP 7700<br/>इंडेक्स + खोज| meili
server -.->|SMTP 1025 dev| mail
server -.->|HTTPS| tmdb
server -->|FS पढ़ना| fs
server -->|FS पढ़ना/लिखना| cache
server -->|FS पढ़ना/लिखना| data
प्रोडक्शन इस डेवलपमेंट बनावट से अलग है
इंस्टॉल वाली compose फ़ाइल एक ही सर्विस बताती है, evey। कोई अलग evey-web
container नहीं है (न ही सिर्फ़-dev वाले mailcatcher और Metro bundler): binary बना
हुआ वेब इंटरफ़ेस /srv से देता है, इसलिए रिवर्स प्रॉक्सी सब कुछ, इंटरफ़ेस और
API, उसे :9091 पर भेजता है। Postgres और Meilisearch उसी container के अंदर चलते
हैं, docker/entrypoint.sh की निगरानी में: PostgreSQL 17, जिसका डेटा
/data/postgres में है, और Meilisearch binary (Dockerfile, meili-download
stage, 1.12.8 पर तय), जिसका इंडेक्स /data/meili में है। repository की अपनी
docker-compose.yml अलग postgres और caddy सर्विस रखती है, जो
एडवांस्ड डिप्लॉयमेंट में बताई गई हैं। देखें
इंस्टॉल और रिवर्स प्रॉक्सी कॉन्फ़िगरेशन।
स्तर 3 - Component (evey-server पर zoom)¶
Go binary के अंदर hexagonal परतें। driving adapter ऐप्लिकेशन सर्विस को उनके interface से कॉल करते हैं; ऐप्लिकेशन सर्विस सिर्फ़ domain port पर निर्भर हैं; driven adapter उन port को लागू करते हैं।
flowchart LR
subgraph driving[Driving adapter - अंदर आने वाले]
grpc[grpcapi<br/>internal/adapters/driving/grpcapi<br/>हर context के handler]
gw[grpc-gateway<br/>JSON/HTTP <-> gRPC<br/>proto से बना]
http[http<br/>internal/adapters/driving/http<br/>static, SSE, HLS,<br/>auth_middleware.go]
plug[pluginhost<br/>internal/adapters/driving/pluginhost<br/>Unix socket पर gRPC<br/>peer-credential से पहचान]
end
subgraph app[ऐप्लिकेशन परत - internal/app]
direction TB
a_lib[library]
a_med[media]
a_meta[metadata]
a_play[playback]
a_trans[transcoding]
a_sub[subtitle]
a_mkv[mkv]
a_music[music]
a_photo[photo]
a_user[user]
a_upd[updatecheck]
a_misc[favorite, playlist, collection,<br/>feed, activity,<br/>chromecast, syncplay,<br/>plugin, notification, webhook,<br/>backup, dlna, config, mediarequest,<br/>maintenance, presence, qoe, loadtest,<br/>demo]
end
subgraph domain[Domain केंद्र - internal/domain]
d_models[Aggregate + Value Object<br/>हर bounded context के]
d_ports[बाहर जाने वाले port<br/>Repo - command/query बँटे हुए,<br/>EventBus, TransactionalUnit,<br/>ServerConfigRepository]
end
subgraph driven[Driven adapter - बाहर जाने वाले]
pgad[postgres<br/>repo + migration<br/>shared.TransactionalUnit]
meiad[meilisearch<br/>खोज इंडेक्स]
fsad[filesystem<br/>scanner, parser, watcher]
ffad[ffmpeg<br/>transcoder, trickplay]
mkvad[mkvgo<br/>MKV के काम]
metaad[metadata<br/>TMDB, OMDb, IMDbAPI,<br/>TVmaze client]
introad[introproviders<br/>IntroDB + TIDB की कड़ी]
itunad[itunes<br/>एल्बम artwork]
mbad[musicbrainz<br/>एल्बम artwork]
ociad[ociregistry<br/>प्रकाशित tag]
probead[prober<br/>mkvgo तेज़ रास्ता,<br/>ffprobe fallback]
searchad[search<br/>meili के ऊपर मज़बूत परत]
osad[opensubtitles]
emad[email]
wpad[webpush]
whad[webhooksender]
prad[pluginrunner]
end
grpc --> app
gw --> grpc
http --> app
plug --> app
app --> d_ports
app --> d_models
d_ports -. लागू करता है .- pgad
d_ports -. लागू करता है .- meiad
d_ports -. लागू करता है .- fsad
d_ports -. लागू करता है .- ffad
d_ports -. लागू करता है .- mkvad
d_ports -. लागू करता है .- metaad
d_ports -. लागू करता है .- introad
d_ports -. लागू करता है .- probead
a_music -. app परत का interface .- itunad
a_music -. app परत का interface .- mbad
a_upd -. app परत का interface .- ociad
d_ports -. लागू करता है .- searchad
d_ports -. लागू करता है .- osad
d_ports -. लागू करता है .- emad
d_ports -. लागू करता है .- wpad
d_ports -. लागू करता है .- whad
d_ports -. लागू करता है .- prad
pgad --> pg2[(PostgreSQL)]
meiad --> me2[(Meilisearch)]
fsad --> fs2[(/media bind mount)]
ffad --> cc2[(evey-cache + ffmpeg binary)]
metaad -.-> ext2[[TMDB / OMDb / IMDbAPI / TVmaze]]
introad -.-> ext3[[IntroDB / TIDB]]
itunad -.-> ext4[[iTunes Search / MusicBrainz]]
mbad -.-> ext4
ociad -.-> ext5[[OCI registry]]
Goroutine के बारे में:
- gRPC सर्वर (
grpc.Server.Serve) और HTTP सर्वर (gateway + static), दोनोंcmd/eveyकी मुख्य जोड़ से अलग अपनी-अपनी goroutine में चलते हैं। app/libraryके स्कैन काम एक worker pool पर चलते हैं (हर सक्रिय लाइब्रेरी स्कैन के लिए एक goroutine, देखेंlibrary.ScanService)।- ffmpeg के काम driven
ffmpegadapter (ffmpeg.Transcoder.Start)internal/pkg/procforkके ज़रिए fork करता है, हर काम के लिए एक निगरानी वाली goroutine जो प्रोसेस का stderr खाली करती रहती है। shared.EventBussubscriber को asynchronous रूप से चलाता है: हर subscriber अपनी goroutine में चलता है, ताकि धीमा subscriber कभी publisher को न रोके। bus अपना कोई transaction नहीं खोलता; यह handler का काम है।pluginrunnerहर प्लगइन के लिए एक subprocess शुरू करता है; iframe प्लगइन उपयोगकर्ता के ब्राउज़र के अंदर रहते हैं औरapp/pluginके दिए साइन किए गए token से सर्वर से संपर्क करते हैं।
स्तर 3 (अतिरिक्त) - स्कैन pipeline पर component zoom¶
वह मुख्य रास्ता (hot path) जो डिस्क की फ़ाइलों को खोजने लायक, जानकारी से भरे मीडिया में बदलता है।
ठोस तीर सीधी कॉल हैं; बिंदीदार तीर bus पर domain event हैं। खोज का इंडेक्स बनाना
ScanService से एक सीधी port कॉल है, event नहीं।
flowchart LR
user([एडमिन शुरू करता है<br/>POST /api/v1/libraries/:id/scan])
subgraph driving2[driving]
h[grpcapi<br/>library_handler.go]
end
subgraph appLayer[internal/app/library]
svc[ScanService<br/>worker pool,<br/>हर लाइब्रेरी के लिए 1 goroutine]
end
subgraph drivenScan[driven]
scanner[filesystem.Scanner<br/>walk + parser]
watcher[filesystem.FSWatcher<br/>fsnotify, थोड़ा-थोड़ा]
end
subgraph domainScan[domain/library]
repo[(MediaItemCommandRepo + MediaItemQueryRepo port)]
bus[(EventBus port)]
end
subgraph persist[driven सहेजना]
pgrepo[postgres.MediaItemRepository<br/>TransactionalUnit के अंदर]
searchidx[meilisearch.SearchAdapter<br/>library.SearchIndex लागू करता है]
end
subgraph consumers[Event लेने वाले - हर एक अपनी goroutine में]
meta[app/metadata<br/>TMDB/OMDb से जानकारी]
trick[app/library<br/>trickplay worker]
intro[app/library<br/>इंट्रो की पहचान]
feed[app/feed<br/>genre cache हटाना]
notif[app/notification<br/>नए मीडिया की सूचना]
wh[app/webhook<br/>सब-कुछ-लेने वाला, बाहर POST]
end
user --> h --> svc
svc --> scanner
watcher --> svc
scanner --> svc
svc --> repo
repo --> pgrepo
svc --> searchidx
svc --> bus
bus -. MediaItemAdded .-> meta
bus -. MediaItemAdded .-> trick
bus -. MediaItemAdded .-> intro
bus -. MediaItemAdded .-> feed
bus -. MediaItemAdded .-> notif
bus -. MediaItemAdded .-> wh
trick -. TrickplayGenerated .-> intro
meta --> repo
trick --> ffstore[(evey-cache<br/>trickplay sprite)]
इसे ऊपर के "आम डेटा का रास्ता" वाले हिस्से के साथ पढ़ें: प्लेबैक का रास्ता उसका जोड़ीदार पढ़ने वाला रास्ता है; स्कैन pipeline लिखने वाला रास्ता है, जो वह मीडिया इंडेक्स तैयार करता है जिससे पढ़ने वाला रास्ता डेटा भेजता है।
आगे पढ़ें¶
- DEV.md - build, चलाना, migration, proto बनाना
- कोड का नक्शा - आम बदलावों के लिए कौन सी फ़ाइलें छूनी हैं
- प्लेबैक पाइपलाइन - HLS वीडियो भेजने का पढ़ने वाला रास्ता, गहराई से
- सर्वर API - बनाया गया API रेफ़रेंस
- फ़ैसलों के रिकॉर्ड - आर्किटेक्चर के फ़ैसलों के रिकॉर्ड