@@ -87,7 +87,7 @@ func Run(config Config) error {
8787 },
8888 })
8989 if err != nil {
90- panic ( err )
90+ return fmt . Errorf ( "[Bootstrap] Kafka publisher 초기화 실패: %w" , err )
9191 }
9292 eventPublishers = append (eventPublishers , kafkaPublisher )
9393 defer func () {
@@ -108,7 +108,7 @@ func Run(config Config) error {
108108 },
109109 })
110110 if err != nil {
111- panic ( err )
111+ return fmt . Errorf ( "[Bootstrap] RabbitMQ writer 초기화 실패: %w" , err )
112112 }
113113 eventPublishers = append (eventPublishers , rabbitmqWriter )
114114 defer func () {
@@ -183,13 +183,13 @@ func Run(config Config) error {
183183 prefix := config .HTTP .GlobalPrefix
184184 if prefix != "" {
185185 if ! strings .HasPrefix (prefix , "/" ) {
186- panic ("HTTP GlobalPrefix는 '/'로 시작해야 합니다" )
186+ return fmt . Errorf ("HTTP GlobalPrefix는 '/'로 시작해야 합니다" )
187187 }
188188 if strings .Contains (prefix , ":" ) {
189- panic ("Path 파라미터는 HTTP GlobalPrefix에서 사용될 수 없습니다" )
189+ return fmt . Errorf ("Path 파라미터는 HTTP GlobalPrefix에서 사용될 수 없습니다" )
190190 }
191191 if strings .Contains (prefix , "*" ) {
192- panic ("와일드카드는 HTTP GlobalPrefix에서 사용될 수 없습니다" )
192+ return fmt . Errorf ("와일드카드는 HTTP GlobalPrefix에서 사용될 수 없습니다" )
193193 }
194194 prefix = strings .TrimSuffix (prefix , "/" )
195195 log .Printf ("[Bootstrap] HTTP GlobalPrefix 적용: %s" , prefix )
@@ -212,6 +212,9 @@ func Run(config Config) error {
212212 resolved := make ([]core.Interceptor , len (route .Interceptors ))
213213 for i , interceptor := range route .Interceptors {
214214 interceptorType := reflect .TypeOf (interceptor )
215+ if interceptorType == nil {
216+ return fmt .Errorf ("[Bootstrap] Route Interceptor[%d]가 nil입니다" , i )
217+ }
215218 value := reflect .ValueOf (interceptor )
216219
217220 // 같은 타입의 인터셉터 로깅은 한 번만 남긴다.
@@ -225,7 +228,7 @@ func Run(config Config) error {
225228
226229 inst , err := container .Resolve (interceptorType )
227230 if err != nil {
228- panic ( err )
231+ return fmt . Errorf ( "[Bootstrap] Route Interceptor 생성 실패: %w" , err )
229232 }
230233 resolved [i ] = inst .(core.Interceptor )
231234 } else {
@@ -238,10 +241,15 @@ func Run(config Config) error {
238241 }
239242
240243 meta .Interceptors = resolved
241- fullPath := joinPath (prefix , route .Path )
244+ fullPath , err := joinPath (prefix , route .Path )
245+ if err != nil {
246+ return err
247+ }
242248 log .Printf ("[Bootstrap] HTTP 라우트 등록 : (%s) %s" , route .Method , fullPath )
243249
244- assertNoAmbiguousRoute (route .Method , fullPath , registeredPathsByMethod [route .Method ])
250+ if err := assertNoAmbiguousRoute (route .Method , fullPath , registeredPathsByMethod [route .Method ]); err != nil {
251+ return err
252+ }
245253 registeredPathsByMethod [route .Method ] = append (registeredPathsByMethod [route .Method ], fullPath )
246254
247255 router .Register (route .Method , fullPath , meta )
@@ -250,8 +258,7 @@ func Run(config Config) error {
250258 log .Println ("[Bootstrap] 컨트롤러 의존성 Warm-up 시작" )
251259 // Warm-Up Component
252260 if err := container .WarmUp (router .ControllerTypes ()); err != nil {
253- // Warm-up 실패시 panic
254- panic (err )
261+ return fmt .Errorf ("[Bootstrap] HTTP 컨트롤러 Warm-up 실패: %w" , err )
255262 }
256263
257264 log .Println ("[Bootstrap] 실행 파이프라인 구성" )
@@ -319,13 +326,16 @@ func Run(config Config) error {
319326 for _ , interceptor := range ordered {
320327 v := reflect .ValueOf (interceptor )
321328 t := reflect .TypeOf (interceptor )
329+ if t == nil {
330+ return fmt .Errorf ("[Bootstrap] Interceptor가 nil입니다" )
331+ }
322332
323333 if t .Kind () == reflect .Pointer && v .IsNil () {
324334 log .Printf ("[Bootstrap] Interceptor %s가 컨테이너에서 생성됐습니다." , t .Elem ().Name ())
325335
326336 inst , err := container .Resolve (t )
327337 if err != nil {
328- panic ( err )
338+ return fmt . Errorf ( "[Bootstrap] Interceptor 생성 실패: %w" , err )
329339 }
330340
331341 httpPipeline .AddInterceptor (inst .(core.Interceptor ))
@@ -351,7 +361,7 @@ func Run(config Config) error {
351361 defer wsRuntime .Stop ()
352362
353363 // Echo Transport Hook으로 마운트
354- config . TransportHooks = append ( config . TransportHooks , func (e any ) {
364+ wsMountHook := func (e any ) {
355365 echoInstance , ok := e .(* echo.Echo )
356366 if ! ok {
357367 return
@@ -363,7 +373,8 @@ func Run(config Config) error {
363373 return nil
364374 })
365375 }
366- })
376+ }
377+ config .TransportHooks = append ([]func (any ){wsMountHook }, config .TransportHooks ... )
367378 }
368379
369380 // Echo Adapter
@@ -390,7 +401,7 @@ func Run(config Config) error {
390401 consumerTypes = append (consumerTypes , reg .Meta .ControllerType )
391402 }
392403 if err := container .WarmUp (consumerTypes ); err != nil {
393- panic ( err )
404+ return fmt . Errorf ( "[Bootstrap] Consumer 컨트롤러 Warm-up 실패: %w" , err )
394405 }
395406 }
396407
@@ -419,7 +430,7 @@ func Run(config Config) error {
419430 )
420431
421432 if err := runtime .Validate (); err != nil {
422- panic ( err )
433+ return fmt . Errorf ( "[Bootstrap] Kafka consumer 검증 실패: %w" , err )
423434 }
424435
425436 forwardConsumerErrors ("Kafka" , runtime , consumerErrCh )
@@ -451,7 +462,7 @@ func Run(config Config) error {
451462 )
452463
453464 if err := runtime .Validate (); err != nil {
454- panic ( err )
465+ return fmt . Errorf ( "[Bootstrap] RabbitMQ consumer 검증 실패: %w" , err )
455466 }
456467
457468 forwardConsumerErrors ("RabbitMQ" , runtime , consumerErrCh )
@@ -539,23 +550,23 @@ func Run(config Config) error {
539550 return nil
540551}
541552
542- func joinPath (prefix , path string ) string {
553+ func joinPath (prefix , path string ) ( string , error ) {
543554 if path == "" {
544- panic ("라우트 Path는 비어있을 수 없습니다" )
555+ return "" , fmt . Errorf ("라우트 Path는 비어있을 수 없습니다" )
545556 }
546557
547558 if ! strings .HasPrefix (path , "/" ) {
548559 path = "/" + path
549560 }
550561
551562 if prefix == "" {
552- return path
563+ return path , nil
553564 }
554565
555- return prefix + path
566+ return prefix + path , nil
556567}
557568
558- func assertNoAmbiguousRoute (method , newPath string , existing []string ) {
569+ func assertNoAmbiguousRoute (method , newPath string , existing []string ) error {
559570 newSegs := splitPathForValidation (newPath )
560571
561572 for _ , oldPath := range existing {
@@ -583,12 +594,13 @@ func assertNoAmbiguousRoute(method, newPath string, existing []string) {
583594 }
584595
585596 if overlaps {
586- panic ( fmt .Sprintf (
597+ return fmt .Errorf (
587598 "[Router] 모호한 라우트가 감지되었습니다 (부트 타임): method=%s, 신규=%s 가 기존=%s 와 충돌합니다" ,
588599 method , newPath , oldPath ,
589- ))
600+ )
590601 }
591602 }
603+ return nil
592604}
593605
594606func splitPathForValidation (path string ) []string {
@@ -613,13 +625,19 @@ func isPathParam(seg string) bool {
613625// forwardConsumerErrors는 특정 런타임의 치명적 에러를 공용 채널로 전달한다.
614626func forwardConsumerErrors (name string , runtime * consumer.Runtime , out chan <- error ) {
615627 go func () {
616- if err := <- runtime .Errors (); err != nil {
628+ select {
629+ case err := <- runtime .Errors ():
630+ if err == nil {
631+ return
632+ }
617633 wrapped := fmt .Errorf ("[Bootstrap] %s consumer runtime error: %w" , name , err )
618634 select {
619635 case out <- wrapped :
620636 default :
621637 log .Printf ("%v (consumerErrCh가 가득 차 전파하지 못했습니다)" , wrapped )
622638 }
639+ case <- runtime .Done ():
640+ return
623641 }
624642 }()
625643}
0 commit comments