Showing preview only (1,737K chars total). Download the full file or copy to clipboard to get everything.
Repository: PacktPublishing/Hands-On-Dependency-Injection-in-Go
Branch: master
Commit: de70ca622e41
Files: 656
Total size: 1.5 MB
Directory structure:
gitextract_t7lucnow/
├── .gitignore
├── LICENSE
├── README.md
├── ch01/
│ ├── 01_defining_depenency_injection/
│ │ ├── 01_interface.go
│ │ ├── 02_function_literal.go
│ │ ├── 03_test_without_nfs_test.go
│ │ └── 04_fail_test_without_nfs_test.go
│ └── 02_code_smells/
│ ├── 01_code_bloat/
│ │ └── 01_switch_type.go
│ ├── 02_resistance_to_change/
│ │ └── 01_shotgun_surgey.go
│ ├── 03_wasted_effort/
│ │ ├── 01_excessive_comments.go
│ │ └── 02_complicated_go.go
│ └── 04_tight_coupling/
│ ├── 01_circular_dependencies/
│ │ ├── config/
│ │ │ └── config.go
│ │ └── payment/
│ │ └── currency.go
│ ├── 02_object_orgy.go
│ └── 03_feature_envy.go
├── ch02/
│ ├── 01_single_responsibility_principle/
│ │ ├── 01_responsibility_vs_change.go
│ │ ├── 02_responsibility_vs_change.go
│ │ ├── 03_responsibility_vs_change.go
│ │ ├── 04_long_method.go
│ │ ├── 04_long_method_test.go
│ │ └── 05_srp_method.go
│ ├── 02_open_closed_principle/
│ │ ├── 01_open_closed_failure.go
│ │ ├── 02_open_closed_success.go
│ │ ├── 03_shotgun_surgery.go
│ │ ├── 04_after_shotgun_surgery.go
│ │ ├── 05_composition.go
│ │ ├── 06_handler_struct.go
│ │ └── 07_handler_func.go
│ ├── 03_liskov_substitution_principle/
│ │ ├── 01_violation/
│ │ │ └── example.go
│ │ ├── 02_fixed/
│ │ │ └── example.go
│ │ ├── 03_fixed/
│ │ │ └── example.go
│ │ ├── 04_behaviour.go
│ │ └── 05_behaviour_fixed.go
│ └── 04_interface_segregation_principle/
│ ├── 01_fat_interface.go
│ ├── 02_thin_interface.go
│ ├── 03_repeated_inputs.go
│ ├── 04_repeated_inputs.go
│ ├── 05_repeated_inputs.go
│ └── 06_implicit_interfaces.go
├── ch03/
│ ├── 01_optimizing_for_humans/
│ │ ├── 01_not_so_simple.go
│ │ ├── 02_start_simple.go
│ │ ├── 03_too_abstract.go
│ │ ├── 04_common_concept.go
│ │ ├── 05_boolean_param.go
│ │ ├── 06_hidden_boolean.go
│ │ ├── 07_wide_formatter.go
│ │ ├── 08_thin_formatters.go
│ │ └── 09_extra_config.go
│ ├── 02_unit_tests/
│ │ ├── 01_loader.go
│ │ ├── 02_language_feature.go
│ │ ├── 03_simple_test.go
│ │ ├── 04_test_from_api.go
│ │ ├── 05_repeated_code.go
│ │ ├── 06_tdt.go
│ │ ├── 07_person_loader.go
│ │ ├── 08_stub.go
│ │ ├── 09_stub_tdt.go
│ │ └── 10_mocks.go
│ ├── 03_test_induced_damage/
│ │ ├── 01_io_closer.go
│ │ └── 02_json.go
│ ├── 04_visualizing_dependencies/
│ │ └── depgraph.sh
│ └── fake.go
├── ch04/
│ ├── 01_welcome/
│ │ ├── 01_bad_names.go
│ │ ├── 02_improved_names.go
│ │ ├── 03_long_method.go
│ │ ├── 04_long_method_test.go
│ │ └── 05_short_methods.go
│ ├── 03_known_issues/
│ │ ├── 01_data_and_rest/
│ │ │ └── get_example.go
│ │ └── 02_config_coupling/
│ │ ├── config.go
│ │ └── currency/
│ │ └── currency.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── common_test.go
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch05/
│ ├── 02_advantages/
│ │ ├── 01_function.go
│ │ ├── 02_monkey_patched.go
│ │ ├── 03_injected_lambda.go
│ │ ├── 04_as_object.go
│ │ ├── 05_math_rand.go
│ │ └── 06_math_rand_test.go
│ ├── 03_applying/
│ │ ├── 01_simple_sqlmock_test.go
│ │ └── 02_load.go
│ ├── 04_disadvantages/
│ │ ├── 01_verbose.go
│ │ ├── 02_verbose_test.go
│ │ └── 03_refactored_test.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── common_test.go
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch06/
│ ├── 01_constructor_injection/
│ │ ├── 01_welcome_email.go
│ │ ├── 01_welcome_email_test.go
│ │ ├── 02_mailer_interface.go
│ │ ├── 03_sender_interface.go
│ │ └── 05_duck_typing.go
│ ├── 02_advantages/
│ │ ├── 01_easy_to_implement.go
│ │ ├── 01_easy_to_implement_example_test.go
│ │ ├── 02_easy_to_implement.go
│ │ ├── 02_easy_to_implement_example_test.go
│ │ ├── 03_predictable.go
│ │ ├── 04_predictable.go
│ │ ├── 05_encapsulation.go
│ │ └── 06_encapsulation.go
│ ├── 03_applying/
│ │ ├── 01/
│ │ │ ├── 01_register_handler_before.go
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ └── register/
│ │ │ └── register.go
│ │ ├── 02/
│ │ │ ├── 01_register_handler.go
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ └── register/
│ │ │ └── register.go
│ │ ├── 03/
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ ├── mock_register_model_test.go
│ │ │ └── register_test.go
│ │ ├── 04/
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── register.go
│ │ │ └── register_test.go
│ │ └── 05/
│ │ ├── data/
│ │ │ └── person.go
│ │ ├── fakes.go
│ │ ├── get/
│ │ │ └── getter.go
│ │ ├── list/
│ │ │ └── lister.go
│ │ ├── register/
│ │ │ └── registerer.go
│ │ └── server.go
│ ├── 04_disadvantages/
│ │ ├── 01_lots_of_changes.go
│ │ ├── 02_overuse.go
│ │ ├── 03_non_obvious.go
│ │ ├── 04_non_obvious_example_test.go
│ │ └── 05_constructors.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ ├── fake.go
│ └── pcov-html
├── ch07/
│ ├── 01_method_injection/
│ │ ├── 01_fprint.go
│ │ ├── 02_http_request.go
│ │ ├── 03_fprint.go
│ │ ├── 04_http_request.go
│ │ ├── 05_timestamp_writer_v1.go
│ │ ├── 06_timestamp_writer_v2.go
│ │ └── 07_timestamp_writer_v3.go
│ ├── 02_advantages/
│ │ ├── 01_handler_v1.go
│ │ ├── 02_handler_v2.go
│ │ ├── 03_handler_v3.go
│ │ ├── 04_context_influence.go
│ │ └── 05_person_loader.go
│ ├── 04_disadvantages/
│ │ ├── 01_data_struct.go
│ │ ├── 02_ux_improvement.go
│ │ ├── 03_many_params.go
│ │ └── 04_many_params_v2.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch08/
│ ├── 01_config_injection/
│ │ ├── 01_long_constructor.go
│ │ ├── 02_by_config_example.go
│ │ └── 03_shared_params.go
│ ├── 02_advantages/
│ │ ├── 01_injected_config/
│ │ │ ├── 01.go
│ │ │ └── 01_test.go
│ │ ├── 02_config_injection/
│ │ │ ├── 02.go
│ │ │ └── 02_test.go
│ │ ├── 03_long_constructor.go
│ │ ├── 04_by_config_example.go
│ │ ├── config/
│ │ │ └── config.go
│ │ ├── logging/
│ │ │ └── logger.go
│ │ └── stats/
│ │ └── stats.go
│ ├── 03_applying/
│ │ ├── 01_define_register_config.go
│ │ ├── 02_register_with_config_injection.go
│ │ ├── 03_model_before_data_changes.go
│ │ ├── 04_test_config_link_to_config_package.go
│ │ ├── 05_result_payload.json
│ │ └── 06_simple_test_server.go
│ ├── 04_disadvantages/
│ │ ├── 01_leaking_details.go
│ │ ├── 02_hiding_details.go
│ │ ├── 03_unclear_lifecycle.go
│ │ ├── 04_clear_lifecycle.go
│ │ └── 05_layers.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch09/
│ ├── 01_jit_injection/
│ │ ├── 01_injecting_db.go
│ │ ├── 01_injecting_db_test.go
│ │ ├── 02_injecting_business_logic.go
│ │ ├── 03_injecting_db_jit.go
│ │ ├── 03_injecting_db_jit_test.go
│ │ └── 04_noop_debugger.go
│ ├── 02_advantages/
│ │ ├── 01_long_constructor.go
│ │ ├── 02_short_constructor.go
│ │ ├── 03_optional_dep_without_jitdi.go
│ │ ├── 04_optional_dep_with_jitdi.go
│ │ ├── 05_loader.go
│ │ ├── 06_global_variable/
│ │ │ └── 06_global_variable.go
│ │ ├── 07_global_variable_jit/
│ │ │ ├── 07_global_variable_jit.go
│ │ │ └── 07_global_variable_jit_test.go
│ │ ├── 08_car_v1.go
│ │ └── 09_car_v2.go
│ ├── 03_applying/
│ │ ├── 01_commands.sh
│ │ ├── 02_coverage.txt
│ │ └── 03_initial_dao.go
│ ├── 04_disadvantages/
│ │ ├── 01_uncertain_init_state.go
│ │ ├── 02_certain_init_state.go
│ │ ├── 03_cpool_slow_constructor.go
│ │ └── 04_get_pool_with_once.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── dao.go
│ │ │ │ │ ├── data.go
│ │ │ │ │ ├── data_test.go
│ │ │ │ │ └── tracker.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ ├── go_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ ├── list_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ └── register/
│ │ │ │ ├── mock_my_saver_test.go
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch10/
│ ├── 01_intro_to_wire/
│ │ ├── 01_simple/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 02_params/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 03_error/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ └── 04_without_pset/
│ │ ├── main.go
│ │ ├── wire.go
│ │ └── wire_gen.go
│ ├── 02_advantages/
│ │ ├── 01_dig/
│ │ │ └── main.go
│ │ └── 02_instantiation_order/
│ │ ├── handler.go
│ │ ├── injectors.go
│ │ ├── main.go
│ │ ├── model.go
│ │ ├── providers.go
│ │ └── wire_gen.go
│ ├── 03_applying/
│ │ ├── 01_before_config/
│ │ │ └── main.go
│ │ ├── 02_after_config/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 03_after_exchange/
│ │ │ ├── main.go
│ │ │ └── wire.go
│ │ ├── 04_after_model/
│ │ │ ├── main.go
│ │ │ └── wire.go
│ │ ├── 05_after_rest/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 06_build_tag.go
│ │ ├── 06_build_tag_inverse.go
│ │ └── 06_main.go
│ ├── 04_disadvantages/
│ │ └── 01_complexity/
│ │ └── main.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── dao.go
│ │ │ │ │ ├── data.go
│ │ │ │ │ ├── data_test.go
│ │ │ │ │ └── tracker.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ ├── go_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ ├── list_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ └── register/
│ │ │ │ ├── mock_my_saver_test.go
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ ├── main.go
│ │ ├── main_test.go
│ │ ├── wire.go
│ │ └── wire_gen.go
│ └── fake.go
├── ch11/
│ ├── 01_di_induced_damage/
│ │ ├── 01_long_param/
│ │ │ └── 01_long_param.go
│ │ ├── 02_long_param/
│ │ │ └── 01_long_param.go
│ │ ├── 03_long_param/
│ │ │ └── 01_long_param.go
│ │ ├── 04_long_param/
│ │ │ ├── 01_long_param.go
│ │ │ └── 01_long_param_test.go
│ │ ├── 05_inject_sql/
│ │ │ └── 01_interface.go
│ │ ├── 06_inject_sql/
│ │ │ ├── 01_interface.go
│ │ │ ├── 02_implementation.go
│ │ │ ├── 02_implementation_test.go
│ │ │ ├── dao.go
│ │ │ ├── data.go
│ │ │ └── data_test.go
│ │ ├── 07_needless_indirection/
│ │ │ └── example_test.go
│ │ ├── 08_needless_indirection/
│ │ │ ├── 01_mux.go
│ │ │ ├── 01_mux_test.go
│ │ │ └── mock_my_mux_test.go
│ │ ├── 09_needless_indirection/
│ │ │ ├── 01_mux.go
│ │ │ └── 01_mux_test.go
│ │ ├── 10_needless_indirection/
│ │ │ ├── 01_mux_e2e.go
│ │ │ └── 01_mux_e2e_test.go
│ │ └── 11_service_locator/
│ │ ├── 01_service_locator.go
│ │ └── 02_usage.go
│ ├── 02_premature_future/
│ │ └── get.go
│ ├── 03_mocking_http_requests/
│ │ ├── converter.go
│ │ ├── converter_test.go
│ │ └── mock_requester_test.go
│ └── acme/
│ ├── internal/
│ │ ├── config/
│ │ │ ├── config.go
│ │ │ └── config_test.go
│ │ ├── logging/
│ │ │ └── logging.go
│ │ ├── modules/
│ │ │ ├── data/
│ │ │ │ ├── dao.go
│ │ │ │ ├── data.go
│ │ │ │ ├── data_test.go
│ │ │ │ └── tracker.go
│ │ │ ├── exchange/
│ │ │ │ ├── converter.go
│ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ └── converter_int_bounday_test.go
│ │ │ ├── get/
│ │ │ │ ├── get.go
│ │ │ │ ├── go_test.go
│ │ │ │ └── mock_my_loader_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ ├── list_test.go
│ │ │ │ └── mock_my_loader_test.go
│ │ │ └── register/
│ │ │ ├── mock_exchanger_test.go
│ │ │ ├── mock_my_saver_test.go
│ │ │ ├── register.go
│ │ │ └── register_test.go
│ │ └── rest/
│ │ ├── get.go
│ │ ├── get_test.go
│ │ ├── list.go
│ │ ├── list_test.go
│ │ ├── mock_get_model_test.go
│ │ ├── mock_list_model_test.go
│ │ ├── mock_register_model_test.go
│ │ ├── not_found.go
│ │ ├── not_found_test.go
│ │ ├── register.go
│ │ ├── register_test.go
│ │ └── server.go
│ ├── main.go
│ ├── main_test.go
│ ├── wire.go
│ └── wire_gen.go
├── ch12/
│ ├── 01_improvements/
│ │ └── 01_test_logging_test.go
│ ├── 03_testing/
│ │ ├── 01_mock_get_model.go
│ │ ├── 02_coverage_ch04.txt
│ │ ├── 03_coverage_ch11.txt
│ │ ├── 04_coverage_config.htm
│ │ ├── 04_coverage_data.htm
│ │ ├── 04_coverage_exchange.htm
│ │ ├── 04_coverage_get.htm
│ │ ├── 04_coverage_list.htm
│ │ ├── 04_coverage_main.htm
│ │ ├── 04_coverage_register.htm
│ │ └── 04_coverage_rest.htm
│ ├── 04_new_service/
│ │ └── 01_data_with_cache/
│ │ ├── dao.go
│ │ ├── data.go
│ │ └── internal/
│ │ ├── cache/
│ │ │ └── cache.go
│ │ └── logging/
│ │ └── logging.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── dao.go
│ │ │ │ │ ├── data.go
│ │ │ │ │ ├── data_test.go
│ │ │ │ │ └── tracker.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ ├── go_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ ├── list_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ └── register/
│ │ │ │ ├── mock_exchanger_test.go
│ │ │ │ ├── mock_my_saver_test.go
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ ├── main.go
│ │ ├── main_test.go
│ │ ├── wire.go
│ │ └── wire_gen.go
│ └── fake.go
├── default-config.json
├── fake.go
├── resources/
│ └── create.sql
└── vendor/
├── github.com/
│ ├── DATA-DOG/
│ │ └── go-sqlmock/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── argument.go
│ │ ├── driver.go
│ │ ├── expectations.go
│ │ ├── expectations_before_go18.go
│ │ ├── expectations_go18.go
│ │ ├── result.go
│ │ ├── rows.go
│ │ ├── rows_go18.go
│ │ ├── sqlmock.go
│ │ ├── sqlmock_go18.go
│ │ ├── statement.go
│ │ └── util.go
│ ├── davecgh/
│ │ └── go-spew/
│ │ ├── LICENSE
│ │ └── spew/
│ │ ├── bypass.go
│ │ ├── bypasssafe.go
│ │ ├── common.go
│ │ ├── config.go
│ │ ├── doc.go
│ │ ├── dump.go
│ │ ├── format.go
│ │ └── spew.go
│ ├── go-sql-driver/
│ │ └── mysql/
│ │ ├── AUTHORS
│ │ ├── CHANGELOG.md
│ │ ├── CONTRIBUTING.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── appengine.go
│ │ ├── buffer.go
│ │ ├── collations.go
│ │ ├── connection.go
│ │ ├── connection_go18.go
│ │ ├── const.go
│ │ ├── driver.go
│ │ ├── dsn.go
│ │ ├── errors.go
│ │ ├── fields.go
│ │ ├── infile.go
│ │ ├── packets.go
│ │ ├── result.go
│ │ ├── rows.go
│ │ ├── statement.go
│ │ ├── transaction.go
│ │ ├── utils.go
│ │ ├── utils_go17.go
│ │ └── utils_go18.go
│ ├── google/
│ │ └── wire/
│ │ ├── AUTHORS
│ │ ├── CODE_OF_CONDUCT.md
│ │ ├── CONTRIBUTING.md
│ │ ├── CONTRIBUTORS
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── wire.go
│ ├── gorilla/
│ │ ├── context/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── context.go
│ │ │ └── doc.go
│ │ └── mux/
│ │ ├── ISSUE_TEMPLATE.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── context_gorilla.go
│ │ ├── context_native.go
│ │ ├── doc.go
│ │ ├── middleware.go
│ │ ├── mux.go
│ │ ├── regexp.go
│ │ ├── route.go
│ │ └── test_helpers.go
│ ├── pmezard/
│ │ └── go-difflib/
│ │ ├── LICENSE
│ │ └── difflib/
│ │ └── difflib.go
│ └── stretchr/
│ ├── objx/
│ │ ├── LICENSE.md
│ │ ├── README.md
│ │ ├── accessors.go
│ │ ├── constants.go
│ │ ├── conversions.go
│ │ ├── doc.go
│ │ ├── map.go
│ │ ├── mutations.go
│ │ ├── security.go
│ │ ├── tests.go
│ │ ├── type_specific_codegen.go
│ │ └── value.go
│ └── testify/
│ ├── LICENSE
│ ├── assert/
│ │ ├── assertion_format.go
│ │ ├── assertion_format.go.tmpl
│ │ ├── assertion_forward.go
│ │ ├── assertion_forward.go.tmpl
│ │ ├── assertions.go
│ │ ├── doc.go
│ │ ├── errors.go
│ │ ├── forward_assertions.go
│ │ └── http_assertions.go
│ ├── mock/
│ │ ├── doc.go
│ │ └── mock.go
│ └── require/
│ ├── doc.go
│ ├── forward_requirements.go
│ ├── require.go
│ ├── require.go.tmpl
│ ├── require_forward.go
│ ├── require_forward.go.tmpl
│ └── requirements.go
├── go.uber.org/
│ └── dig/
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── check_license.sh
│ ├── cycle.go
│ ├── dig.go
│ ├── doc.go
│ ├── error.go
│ ├── glide.yaml
│ ├── internal/
│ │ ├── digreflect/
│ │ │ └── func.go
│ │ └── dot/
│ │ └── graph.go
│ ├── param.go
│ ├── result.go
│ ├── stringer.go
│ ├── types.go
│ └── version.go
├── google.golang.org/
│ └── appengine/
│ ├── LICENSE
│ └── cloudsql/
│ ├── cloudsql.go
│ ├── cloudsql_classic.go
│ └── cloudsql_vm.go
└── vendor.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
config.json
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 Packt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Hands-On Dependency Injection in Go
<a href="https://www.packtpub.com/application-development/hands-dependency-injection-go?utm_source=github&utm_medium=repository&utm_campaign=9781789132762 "><img src="https://d255esdrn735hr.cloudfront.net/sites/default/files/imagecache/ppv4_main_book_cover/B10763_MockupCover_new.png" alt="Hands-On Dependency Injection in Go" height="256px" align="right"></a>
This is the code repository for [Hands-On Dependency Injection in Go](https://www.packtpub.com/application-development/hands-dependency-injection-go?utm_source=github&utm_medium=repository&utm_campaign=9781789132762 ), published by Packt.
**Develop clean Go code that is easier to read, maintain, and test**
## What is this book about?
Hands-On Dependency Injection in Go takes you on a journey, refactoring existing code to adopt dependency injection (DI) using various methods available in Go.
This book covers the following exciting features:
* Understand the benefits of dependency injection
* Explore SOLID design principles and how they relate to Go
* Analyze various dependency injection patterns available in Go
* Leverage DI to produce high quality, loosely coupled Go code
* Refactor existing Go code to adopt dependency injection
* Discover tools to improve your code's testability and test coverage
* Generate and interpret Go dependency graphs
If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1789132762) today!
<a href="https://www.packtpub.com/?utm_source=github&utm_medium=banner&utm_campaign=GitHubBanner"><img src="https://raw.githubusercontent.com/PacktPublishing/GitHub/master/GitHub.png"
alt="https://www.packtpub.com/" border="5" /></a>
## Instructions and Navigations
All of the code is organized into folders. For example, ch02.
The code will look like the following:
```
html, body, #map {
height: 100%;
margin: 0;
padding: 0
}
```
**Following is what you need for this book:**
Hands-On Dependency Injection in Go is for programmers with a few year s experience in any language and a basic understanding of Go. If you wish to produce clean, loosely coupled code that is inherently easier to test, this book is for you.
## Getting the source
The easiest way to obtain the source code is to use `go get`.
This will ensure that the code is placed in the correct directory and should be then runnable and testable.
To download this repo use `go get github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/...`
## Code Organization
In this repository, there is 1 folder for every chapter of the book, named chXX where XX is the chapter number.
The code provided are expanded versions of the code presented in the book. While it will compile and typically
will not throw an error when passed into `go test` it is not designed to be executed.
From chapter 4 onwards, there is an `acme` directory included with the code that chapter.
The `acme` directory is the code for the sample service presented in the book with the changes discussed in that chapter
already applied.
You will also find 2 additional directories in the root of the repository:
* **resources** - this directory contains an SQL file that should be used to populate a MySQL database. This database
is used by the sample service
* **vendor** - this is standard go vendor directory which contains the external packages required by the sample service
## Setting up the MySQL database
The easiest way to create and populate the database required by the sample service is by running the following:
`mysql < ./resources/create.sql`
Depending on your settings you may want to provide a username and password like this:
`mysql -u [your username] -p < ./resources/create.sql`
This will create a database called `acme` with 1 table and 4 records.
## Creating a free account on CurrencyLayer
The sample service uses a free currency conversion service. In order to successfully run all the examples, you will need
to sign up [here](https://currencylayer.com/) and obtain an API Key.
## Configuring the sample service
Now that you have your MySQL and CurrencyLayer credentials you can create a config for the sample service.
1. Copy `default-config.json` (found next to this file) to `config.json`
1. Open `config.json` in your favorite editor
1. Add your database credentials to the `"dsn"` setting. Should be in the form:
`"[username]:[password]@tcp(localhost:3306)/[database name]?autocommit=true"`
1. Add your API Key to the `"exchangeRateAPIKey"` setting. Should be in the form: `"1234567890abcdef1234567890abcdef"`
## Running the sample service for a particular chapter
To run sample service for a particular chapter:
1. First make sure you are in the base of this repository:
`cd $GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/`
1. Use a command similar to the following (which is for ch04):
`ACME_CONFIG=$GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/config.json go run ./ch04/acme/main.go`
### Special instructions for chapters 10-12
As we have multiple files and tests in the `main` package, we cannot use the standard `go run ./ch10/acme/main.go` to run the service.
Instead we need to modify the command to `go run ./ch10/acme/main.go ./ch10/acme/wire_gen.go`
## Running tests for a chapter
To run sample service for a particular chapter you can use a command similar to the follow (which is for ch04):
1. First make sure you are in the base of this repository:
`cd $GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/`
1. Use a command similar to the following (which is for ch04):
`ACME_CONFIG=$GOPATH/src/github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/config.json go test ./ch04/...`
With the following software and hardware list you can run all code files present in the book (Chapter 1-12).
### Software and Hardware List
| Chapter | Software required | OS required |
| -------- | ------------------------------------ | -----------------------------------|
| 1-12 | Go 1.10.x+ | Windows, Mac OS X, and Linux (Any) |
| 4-12 | MySQL 5.7.x+ | Windows, Mac OS X, and Linux (Any) |
| 4-12 | CurrencyLayer | Windows, Mac OS X, and Linux (Any) |
### Related products
* Mastering Go [[Packt]](https://www.packtpub.com/networking-and-servers/mastering-go?utm_source=github&utm_medium=repository&utm_campaign=9781788626545 ) [[Amazon]](https://www.amazon.com/dp/1788626540)
* Go Standard Library Cookbook [[Packt]](https://www.packtpub.com/application-development/go-standard-library-cookbook?utm_source=github&utm_medium=repository&utm_campaign=9781788475273 ) [[Amazon]](https://www.amazon.com/dp/1788475275)
## Get to Know the Author
**Corey Scott**
is a senior software engineer currently living in Melbourne, Australia. He’s been programming professionally since 2000, with the last 5 years spent building large-scale distributed services in Go.
An occasional technical speaker and blogger on a variety of software-related topics, he is passionate about designing and building quality software. He believes that software engineering is a craft that should be honed, debated, and continuously improved. He takes a pragmatic, non-zealot approach to coding and is always up for a good debate about software engineering, continuous delivery, testing, or clean coding.
## Other books by the authors
### Suggestions and Feedback
[Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions.
### Download a free PDF
<i>If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.<br>Simply click on the link to claim your free PDF.</i>
<p align="center"> <a href="https://packt.link/free-ebook/9781789132762">https://packt.link/free-ebook/9781789132762 </a> </p>
================================================
FILE: ch01/01_defining_depenency_injection/01_interface.go
================================================
package defining_depenency_injection
import (
"encoding/json"
"errors"
)
// Saver persists the supplied bytes
type Saver interface {
Save(data []byte) error
}
// SavePerson will validate and persist the supplied person
func SavePerson(person *Person, saver Saver) error {
// validate the inputs
err := person.validate()
if err != nil {
return err
}
// encode person to bytes
bytes, err := person.encode()
if err != nil {
return err
}
// save the person and return the result
return saver.Save(bytes)
}
// Person data object
type Person struct {
Name string
Phone string
}
// validate the person object
func (p *Person) validate() error {
if p.Name == "" {
return errors.New("name missing")
}
if p.Phone == "" {
return errors.New("phone missing")
}
return nil
}
// convert the person into bytes
func (p *Person) encode() ([]byte, error) {
return json.Marshal(p)
}
================================================
FILE: ch01/01_defining_depenency_injection/02_function_literal.go
================================================
package defining_depenency_injection
import (
"errors"
"fmt"
)
// LoadPerson will load the requested person by ID.
// Errors include: invalid ID, missing person and failure to load or decode.
func LoadPerson(ID int, decodePerson func(data []byte) *Person) (*Person, error) {
// validate the input
if ID <= 0 {
return nil, fmt.Errorf("invalid ID '%d' supplied", ID)
}
// load from storage
bytes, err := loadPerson(ID)
if err != nil {
return nil, err
}
// decode bytes and return
return decodePerson(bytes), nil
}
// load person as bytes from storage
func loadPerson(ID int) ([]byte, error) {
// TODO: implement
return nil, errors.New("not implemented")
}
================================================
FILE: ch01/01_defining_depenency_injection/03_test_without_nfs_test.go
================================================
package defining_depenency_injection
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestSavePerson_happyPath(t *testing.T) {
// input
in := &Person{
Name: "Sophia",
Phone: "0123456789",
}
// mock the NFS
mockNFS := &mockSaver{}
mockNFS.On("Save", mock.Anything).Return(nil).Once()
// Call Save
resultErr := SavePerson(in, mockNFS)
// validate result
assert.NoError(t, resultErr)
assert.True(t, mockNFS.AssertExpectations(t))
}
// mock implementation of Saver
type mockSaver struct {
mock.Mock
}
// Save implements Saver
func (m *mockSaver) Save(data []byte) error {
outputs := m.Mock.Called(data)
return outputs.Error(0)
}
================================================
FILE: ch01/01_defining_depenency_injection/04_fail_test_without_nfs_test.go
================================================
package defining_depenency_injection
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestSavePerson_nfsAlwaysFails(t *testing.T) {
// input
in := &Person{
Name: "Sophia",
Phone: "0123456789",
}
// mock the NFS
mockNFS := &mockSaver{}
mockNFS.On("Save", mock.Anything).Return(errors.New("save failed")).Once()
// Call Save
resultErr := SavePerson(in, mockNFS)
// validate result
assert.Error(t, resultErr)
assert.True(t, mockNFS.AssertExpectations(t))
}
================================================
FILE: ch01/02_code_smells/01_code_bloat/01_switch_type.go
================================================
package code_bloat
import (
"strconv"
)
func AppendValue(buffer []byte, in interface{}) []byte {
var value []byte
// convert input to []byte
switch concrete := in.(type) {
case []byte:
value = concrete
case string:
value = []byte(concrete)
case int64:
value = []byte(strconv.FormatInt(concrete, 10))
case bool:
value = []byte(strconv.FormatBool(concrete))
case float64:
value = []byte(strconv.FormatFloat(concrete, 'e', 3, 64))
}
buffer = append(buffer, value...)
return buffer
}
================================================
FILE: ch01/02_code_smells/02_resistance_to_change/01_shotgun_surgey.go
================================================
package _2_resistance_to_change
import (
"database/sql"
"io"
)
// Renderer will render a person to the supplied writer
type Renderer struct{}
func (r Renderer) render(name, phone string, output io.Writer) {
// output the person
}
// Validator will validate the supplied person has all the required fields
type Validator struct{}
func (v Validator) validate(name, phone string) error {
// validate the person
return nil
}
// Saver will save the supplied person to the DB
type Saver struct{}
func (s *Saver) Save(db *sql.DB, name, phone string) {
// save the person to db
}
================================================
FILE: ch01/02_code_smells/03_wasted_effort/01_excessive_comments.go
================================================
package wasted_effort
// Excessive comments
func outputOrderedPeopleA(in []*Person) {
// This code orders people by name.
// In cases where the name is the same, it will order by phone number.
// The sort algorithm used is a bubble sort
// WARNING: this sort will change the items of the input array
for range in {
// ... sort code removed ...
}
outputPeople(in)
}
// Comments replaced with descriptive names
func outputOrderedPeopleB(in []*Person) {
sortPeople(in)
outputPeople(in)
}
func outputPeople(in []*Person) {
// TODO: implement
}
// any special instructions that MUST be documented relating to the sort should go here
func sortPeople(in []*Person) {
// TODO: implement
}
// Person data object
type Person struct {
Name string
Phone string
}
================================================
FILE: ch01/02_code_smells/03_wasted_effort/02_complicated_go.go
================================================
package wasted_effort
import (
"image"
"image/color"
"math"
)
func d(r, v float64, i *image.RGBA, c color.Color) {
for a := float64(0); a < 360; a++ {
ra := math.Pi * 2 * a / 360
x := r*math.Sin(ra) + v
y := r*math.Cos(ra) + v
i.Set(int(x), int(y), c)
}
}
================================================
FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config/config.go
================================================
// +build bad
package config
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment"
)
// Config defines the JSON format of the config file
type Config struct {
// Address is the host and port to bind to.
// Default 0.0.0.0:8080
Address string
// DefaultCurrency is the default currency of the system
DefaultCurrency payment.Currency
}
// Load will load the JSON config from the file supplied
func Load(filename string) (*Config, error) {
// TODO: load currency from file
return nil, errors.New("not implemented yet")
}
================================================
FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment/currency.go
================================================
// +build bad
package payment
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config"
)
// Currency is custom type for currency
type Currency string
// Processor processes payments
type Processor struct {
Config *config.Config
}
// Pay makes a payment in the default currency
func (p *Processor) Pay(amount float64) error {
// TODO: implement me
return errors.New("not implemented yet")
}
================================================
FILE: ch01/02_code_smells/04_tight_coupling/02_object_orgy.go
================================================
package _4_tight_coupling
import (
"errors"
"io/ioutil"
"net/http"
)
type PageLoader struct {
}
func (o *PageLoader) LoadPage(url string) ([]byte, error) {
b := newFetcher()
// check cache
payload, err := b.cache.Get(url)
if err == nil {
// found in cache
return payload, nil
}
// call upstream
resp, err := b.httpClient.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// extract data from HTTP response
payload, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// save to cache asynchronously
go func(key string, value []byte) {
b.cache.Set(key, value)
}(url, payload)
// return
return payload, nil
}
type Fetcher struct {
httpClient http.Client
cache *Cache
}
func newFetcher() *Fetcher {
return &Fetcher{}
}
type Cache struct {
// not implemented
}
func (c *Cache) Get(key string) ([]byte, error) {
// not implemented
return nil, errors.New("not implemented")
}
func (c *Cache) Set(key string, data []byte) error {
// not implemented
return errors.New("not implemented")
}
================================================
FILE: ch01/02_code_smells/04_tight_coupling/03_feature_envy.go
================================================
package _4_tight_coupling
import (
"errors"
"time"
)
type searchRequest struct {
query string
start time.Time
end time.Time
}
func (request searchRequest) validate() error {
if request.query == "" {
return errors.New("search term is missing")
}
if request.start.IsZero() || request.start.After(time.Now()) {
return errors.New("start time is missing or invalid")
}
if request.end.IsZero() || request.end.Before(request.start) {
return errors.New("end time is missing or invalid")
}
return nil
}
type searchResults struct {
result string
}
func doSearchWithEnvy(request searchRequest) ([]searchResults, error) {
// validate request
if request.query == "" {
return nil, errors.New("search term is missing")
}
if request.start.IsZero() || request.start.After(time.Now()) {
return nil, errors.New("start time is missing or invalid")
}
if request.end.IsZero() || request.end.Before(request.start) {
return nil, errors.New("end time is missing or invalid")
}
return performSearch(request)
}
func doSearchWithoutEnvy(request searchRequest) ([]searchResults, error) {
err := request.validate()
if err != nil {
return nil, err
}
return performSearch(request)
}
func performSearch(request searchRequest) ([]searchResults, error) {
// TODO: implement
return nil, errors.New("not implemented")
}
================================================
FILE: ch02/01_single_responsibility_principle/01_responsibility_vs_change.go
================================================
package srp
import (
"fmt"
"io"
)
// CalculatorV1 calculates the test coverage for a directory and it's sub-directories
type CalculatorV1 struct {
// coverage data populated by `Calculate()` method
data map[string]float64
}
// Calculate will calculate the coverage
func (c *CalculatorV1) Calculate(path string) error {
// run `go test -cover ./[path]/...` and store the results
return nil
}
// Output will print the coverage data to the supplied writer
func (c *CalculatorV1) Output(writer io.Writer) {
for path, result := range c.data {
fmt.Fprintf(writer, "%s -> %.1f\n", path, result)
}
}
================================================
FILE: ch02/01_single_responsibility_principle/02_responsibility_vs_change.go
================================================
package srp
import (
"fmt"
"io"
)
// CalculatorV2 calculates the test coverage for a directory and it's sub-directories
type CalculatorV2 struct {
// coverage data populated by `Calculate()` method
data map[string]float64
}
// Calculate will calculate the coverage
func (c *CalculatorV2) Calculate(path string) error {
// run `go test -cover ./[path]/...` and store the results
return nil
}
// Output will print the coverage data to the supplied writer
func (c CalculatorV2) Output(writer io.Writer) {
for path, result := range c.data {
fmt.Fprintf(writer, "%s -> %.1f\n", path, result)
}
}
// OutputCSV will print the coverage data to the supplied writer
func (c CalculatorV2) OutputCSV(writer io.Writer) {
for path, result := range c.data {
fmt.Fprintf(writer, "%s,%.1f\n", path, result)
}
}
================================================
FILE: ch02/01_single_responsibility_principle/03_responsibility_vs_change.go
================================================
package srp
import (
"fmt"
"io"
)
// CalculatorV3 calculates the test coverage for a directory and it's sub-directories
type CalculatorV3 struct {
// coverage data populated by `Calculate()` method
data map[string]float64
}
// Calculate will calculate the coverage
func (c *CalculatorV3) Calculate(path string) error {
// run `go test -cover ./[path]/...` and store the results
return nil
}
func (c *CalculatorV3) getData() map[string]float64 {
// copy and return the map
return nil
}
type Printer interface {
Output(data map[string]float64)
}
type DefaultPrinter struct {
Writer io.Writer
}
// Output implements Printer
func (d *DefaultPrinter) Output(data map[string]float64) {
for path, result := range data {
fmt.Fprintf(d.Writer, "%s -> %.1f\n", path, result)
}
}
type CSVPrinter struct {
Writer io.Writer
}
// Output implements Printer
func (d *CSVPrinter) Output(data map[string]float64) {
for path, result := range data {
fmt.Fprintf(d.Writer, "%s,%.1f\n", path, result)
}
}
================================================
FILE: ch02/01_single_responsibility_principle/04_long_method.go
================================================
package srp
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
)
func loadUserHandlerLong(resp http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
row := DB.QueryRow("SELECT * FROM Users WHERE ID = ?", userID)
person := &Person{}
err = row.Scan(&person.ID, &person.Name, &person.Phone)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
encoder := json.NewEncoder(resp)
err = encoder.Encode(person)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
}
var DB *sql.DB
type Person struct {
ID int64
Name string
Phone string
}
================================================
FILE: ch02/01_single_responsibility_principle/04_long_method_test.go
================================================
package srp
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestLoadUserHandler(t *testing.T) {
// build request
req := &http.Request{
Form: url.Values{},
}
req.Form.Add("UserID", "1234")
// call function under test
resp := httptest.NewRecorder()
loadUserHandlerLong(resp, req)
// validate result
assert.Equal(t, http.StatusOK, resp.Code)
expectedBody := `{"ID":1,"Name":"Bob","Phone":"0123456789"}` + "\n"
assert.Equal(t, expectedBody, resp.Body.String())
}
func TestMain(m *testing.M) {
// create fake DB for this test
var mock sqlmock.Sqlmock
DB, mock, _ = sqlmock.New()
// config fake response
mock.ExpectQuery(".*").WillReturnRows(
sqlmock.NewRows([]string{"ID", "Name", "Phone"}).AddRow(
1, "Bob", "0123456789"))
os.Exit(m.Run())
}
================================================
FILE: ch02/01_single_responsibility_principle/05_srp_method.go
================================================
package srp
import (
"encoding/json"
"net/http"
"strconv"
)
func loadUserHandlerSRP(resp http.ResponseWriter, req *http.Request) {
userID, err := extractIDFromRequest(req)
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
person, err := loadPersonByID(userID)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
outputPerson(resp, person)
}
func extractIDFromRequest(req *http.Request) (int64, error) {
err := req.ParseForm()
if err != nil {
return 0, err
}
return strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
}
func loadPersonByID(userID int64) (*Person, error) {
row := DB.QueryRow("SELECT * FROM Users WHERE userID = ?", userID)
person := &Person{}
err := row.Scan(person.ID, person.Name, person.Phone)
if err != nil {
return nil, err
}
return person, nil
}
func outputPerson(resp http.ResponseWriter, person *Person) {
encoder := json.NewEncoder(resp)
err := encoder.Encode(person)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
}
================================================
FILE: ch02/02_open_closed_principle/01_open_closed_failure.go
================================================
package ocp
import (
"io"
"net/http"
)
func BuildOutputOCPFail(response http.ResponseWriter, format string, person Person) {
var err error
switch format {
case "csv":
err = outputCSV(response, person)
case "json":
err = outputJSON(response, person)
}
if err != nil {
// output a server error and quit
response.WriteHeader(http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
}
// output the person as CSV and return error when failing to do so
func outputCSV(writer io.Writer, person Person) error {
// TODO: implement
return nil
}
// output the person as JSON and return error when failing to do so
func outputJSON(writer io.Writer, person Person) error {
// TODO: implement
return nil
}
// A data transfer object that represents a person
type Person struct {
Name string
Email string
}
================================================
FILE: ch02/02_open_closed_principle/02_open_closed_success.go
================================================
package ocp
import (
"io"
"net/http"
)
func BuildOutputOCPSuccess(response http.ResponseWriter, formatter PersonFormatter, person Person) {
err := formatter.Format(response, person)
if err != nil {
// output a server error and quit
response.WriteHeader(http.StatusInternalServerError)
return
}
response.WriteHeader(http.StatusOK)
}
type PersonFormatter interface {
Format(writer io.Writer, person Person) error
}
// output the person as CSV
type CSVPersonFormatter struct{}
// Format implements the PersonFormatter interface
func (c *CSVPersonFormatter) Format(writer io.Writer, person Person) error {
// TODO: implement
return nil
}
// output the person as JSON
type JSONPersonFormatter struct{}
// Format implements the PersonFormatter interface
func (j *JSONPersonFormatter) Format(writer io.Writer, person Person) error {
// TODO: implement
return nil
}
================================================
FILE: ch02/02_open_closed_principle/03_shotgun_surgery.go
================================================
package ocp
import (
"net/http"
"strconv"
)
func GetUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
// validate inputs
err := req.ParseForm()
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
user := loadUser(userID)
outputUser(resp, user)
}
func DeleteUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
// validate inputs
err := req.ParseForm()
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
deleteUser(userID)
}
func loadUser(userID int64) interface{} {
// TODO: implement
return nil
}
func deleteUser(userID int64) {
// TODO: implement
}
func outputUser(resp http.ResponseWriter, user interface{}) {
// TODO: implement
}
================================================
FILE: ch02/02_open_closed_principle/04_after_shotgun_surgery.go
================================================
package ocp
import (
"errors"
"net/http"
"net/url"
"strconv"
)
func GetUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
// validate inputs
err := req.ParseForm()
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
userID, err := extractUserID(req.Form)
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
user := loadUser(userID)
outputUser(resp, user)
}
func DeleteUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
// validate inputs
err := req.ParseForm()
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
userID, err := extractUserID(req.Form)
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
deleteUser(userID)
}
func extractUserID(values url.Values) (int64, error) {
userID, err := strconv.ParseInt(values.Get("UserID"), 10, 64)
if err != nil {
return 0, err
}
if userID <= 0 {
return 0, errors.New("userID must be positive")
}
return userID, nil
}
================================================
FILE: ch02/02_open_closed_principle/05_composition.go
================================================
package ocp
import (
"database/sql"
)
type rowConverter struct {
}
// populate the supplied Person from *sql.Row or *sql.Rows object
func (d *rowConverter) populate(in *Person, scan func(dest ...interface{}) error) error {
return scan(in.Name, in.Email)
}
type LoadPerson struct {
// compose the row converter into this loader
rowConverter
}
func (loader *LoadPerson) ByID(id int) (Person, error) {
row := loader.loadFromDB(id)
person := Person{}
// call the composed "abstract class"
err := loader.populate(&person, row.Scan)
return person, err
}
func (loader *LoadPerson) loadFromDB(id int) *sql.Row {
// TODO: implement
return nil
}
type LoadAll struct {
// compose the row converter into this loader
rowConverter
}
func (loader *LoadPerson) All() ([]Person, error) {
rows := loader.loadAllFromDB()
defer rows.Close()
var output []Person
for rows.Next() {
person := Person{}
// call the composed "abstract class"
err := loader.populate(&person, rows.Scan)
if err != nil {
return nil, err
}
}
return output, nil
}
func (loader *LoadPerson) loadAllFromDB() *sql.Rows {
// TODO: implement
return nil
}
================================================
FILE: ch02/02_open_closed_principle/06_handler_struct.go
================================================
package ocp
import (
"net/http"
)
// a HTTP health check handler in long form
type healthCheckLong struct {
}
func (h *healthCheckLong) ServeHTTP(resp http.ResponseWriter, _ *http.Request) {
resp.WriteHeader(http.StatusNoContent)
}
func healthCheckLongUsage() {
http.Handle("/health", &healthCheckLong{})
}
================================================
FILE: ch02/02_open_closed_principle/07_handler_func.go
================================================
package ocp
import (
"net/http"
)
// a HTTP health check handler in short form
func healthCheckShort(resp http.ResponseWriter, _ *http.Request) {
resp.WriteHeader(http.StatusNoContent)
}
func healthCheckShortUsage() {
http.Handle("/health", http.HandlerFunc(healthCheckShort))
}
================================================
FILE: ch02/03_liskov_substitution_principle/01_violation/example.go
================================================
package lsp_violation
func Go(vehicle actions) {
if sled, ok := vehicle.(*Sled); ok {
sled.pushStart()
} else {
vehicle.startEngine()
}
vehicle.drive()
}
type actions interface {
drive()
startEngine()
}
type Vehicle struct {
}
func (v Vehicle) drive() {
// TODO: implement
}
func (v Vehicle) startEngine() {
// TODO: implement
}
func (v Vehicle) stopEngine() {
// TODO: implement
}
type Car struct {
Vehicle
}
type Sled struct {
Vehicle
}
func (s Sled) startEngine() {
// override so that is does nothing
}
func (s Sled) stopEngine() {
// override so that is does nothing
}
func (s Sled) pushStart() {
// TODO: implement
}
================================================
FILE: ch02/03_liskov_substitution_principle/02_fixed/example.go
================================================
package fixedv1
func Go(vehicle actions) {
switch concrete := vehicle.(type) {
case poweredActions:
concrete.startEngine()
case unpoweredActions:
concrete.pushStart()
}
vehicle.drive()
}
type actions interface {
drive()
}
type poweredActions interface {
actions
startEngine()
stopEngine()
}
type unpoweredActions interface {
actions
pushStart()
}
type Vehicle struct {
}
func (v Vehicle) drive() {
// TODO: implement
}
type PoweredVehicle struct {
Vehicle
}
func (v PoweredVehicle) startEngine() {
// common engine start code
}
type Car struct {
PoweredVehicle
}
type Sled struct {
Vehicle
}
func (s Sled) pushStart() {
// do nothing
}
================================================
FILE: ch02/03_liskov_substitution_principle/03_fixed/example.go
================================================
package fixedv2
func Go(vehicle actions) {
vehicle.start()
vehicle.drive()
}
type actions interface {
start()
drive()
}
type Car struct {
poweredVehicle
}
func (c Car) start() {
c.poweredVehicle.startEngine()
}
func (c Car) drive() {
// TODO: implement
}
type poweredVehicle struct {
}
func (p poweredVehicle) startEngine() {
// common engine start code
}
type Sled struct {
}
func (s Sled) start() {
// push start
}
func (s Sled) drive() {
// TODO: implement
}
================================================
FILE: ch02/03_liskov_substitution_principle/04_behaviour.go
================================================
package lsp
type Collection interface {
Add(item interface{})
Get(index int) interface{}
}
type CollectionImpl struct {
items []interface{}
}
func (c *CollectionImpl) Add(item interface{}) {
c.items = append(c.items, item)
}
func (c *CollectionImpl) Get(index int) interface{} {
return c.items[index]
}
type ReadOnlyCollection struct {
CollectionImpl
}
func (ro *ReadOnlyCollection) Add(item interface{}) {
// intentionally does nothing
}
================================================
FILE: ch02/03_liskov_substitution_principle/05_behaviour_fixed.go
================================================
package lsp
type ImmutableCollection interface {
Get(index int) interface{}
}
type MutableCollection interface {
ImmutableCollection
Add(item interface{})
}
type ReadOnlyCollectionV2 struct {
items []interface{}
}
func (ro *ReadOnlyCollectionV2) Get(index int) interface{} {
return ro.items[index]
}
type CollectionImplV2 struct {
ReadOnlyCollectionV2
}
func (c *CollectionImplV2) Add(item interface{}) {
c.items = append(c.items, item)
}
================================================
FILE: ch02/04_interface_segregation_principle/01_fat_interface.go
================================================
package isp
import (
"context"
)
type Item struct {
Key string
Payload []byte
}
type FatDbInterface interface {
BatchGetItem(IDs ...int) ([]Item, error)
BatchGetItemWithContext(ctx context.Context, IDs ...int) ([]Item, error)
BatchPutItem(items ...Item) error
BatchPutItemWithContext(ctx context.Context, items ...Item) error
DeleteItem(ID int) error
DeleteItemWithContext(ctx context.Context, item Item) error
GetItem(ID int) (Item, error)
GetItemWithContext(ctx context.Context, ID int) (Item, error)
PutItem(item Item) error
PutItemWithContext(ctx context.Context, item Item) error
Query(query string, args ...interface{}) ([]Item, error)
QueryWithContext(ctx context.Context, query string, args ...interface{}) ([]Item, error)
UpdateItem(item Item) error
UpdateItemWithContext(ctx context.Context, item Item) error
}
type Cache struct {
db FatDbInterface
}
func (c *Cache) Get(key string) interface{} {
// code removed
// load from DB
_, _ = c.db.GetItem(42)
// code removed
return nil
}
func (c *Cache) Set(key string, value interface{}) {
// code removed
// save to DB
_ = c.db.PutItem(Item{})
// code removed
}
================================================
FILE: ch02/04_interface_segregation_principle/02_thin_interface.go
================================================
package isp
type myDB interface {
GetItem(ID int) (Item, error)
PutItem(item Item) error
}
type CacheV2 struct {
db myDB
}
func (c *CacheV2) Get(key string) interface{} {
// code removed
// load from DB
_, _ = c.db.GetItem(42)
// code removed
return nil
}
func (c *CacheV2) Set(key string, value interface{}) {
// code removed
// save from DB
_ = c.db.PutItem(Item{})
// code removed
}
================================================
FILE: ch02/04_interface_segregation_principle/03_repeated_inputs.go
================================================
package isp
import (
"context"
"errors"
)
func Encrypt(ctx context.Context, data []byte) ([]byte, error) {
// As this operation make take too long, we need to be able to kill it
stop := ctx.Done()
result := make(chan []byte, 1)
go func() {
defer close(result)
// pull the encryption key from context
keyRaw := ctx.Value("encryption-key")
if keyRaw == nil {
panic("encryption key not found in context")
}
key := keyRaw.([]byte)
// perform encryption
ciperText := performEncryption(key, data)
// signal complete by sending the result
result <- ciperText
}()
select {
case ciperText := <-result:
// happy path
return ciperText, nil
case <-stop:
// cancelled
return nil, errors.New("operation cancelled")
}
}
func performEncryption(key []byte, data []byte) []byte {
// TODO: implement
return nil
}
================================================
FILE: ch02/04_interface_segregation_principle/04_repeated_inputs.go
================================================
package isp
import (
"errors"
)
type Value interface {
Value(key interface{}) interface{}
}
type Monitor interface {
Done() <-chan struct{}
}
func EncryptV2(keyValue Value, monitor Monitor, data []byte) ([]byte, error) {
// As this operation make take too long, we need to be able to kill it
stop := monitor.Done()
result := make(chan []byte, 1)
go func() {
defer close(result)
// pull the encryption key from Value
keyRaw := keyValue.Value("encryption-key")
if keyRaw == nil {
panic("encryption key not found in context")
}
key := keyRaw.([]byte)
// perform encryption
ciperText := performEncryption(key, data)
// signal complete by sending the result
result <- ciperText
}()
select {
case ciperText := <-result:
// happy path
return ciperText, nil
case <-stop:
// cancelled
return nil, errors.New("operation cancelled")
}
}
================================================
FILE: ch02/04_interface_segregation_principle/05_repeated_inputs.go
================================================
package isp
import (
"context"
)
func UseEncryptV2() {
// create a context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// store the key
ctx = context.WithValue(ctx, "encryption-key", "-secret-")
// call the function
_, _ = EncryptV2(ctx, ctx, []byte("my data"))
}
================================================
FILE: ch02/04_interface_segregation_principle/06_implicit_interfaces.go
================================================
package isp
import (
"fmt"
)
type Talker interface {
SayHello() string
}
type Dog struct{}
// The method implicitly implements the Talker interface
func (d Dog) SayHello() string {
return "Woof!"
}
func Speak() {
var talker Talker
talker = Dog{}
fmt.Print(talker.SayHello())
}
================================================
FILE: ch03/01_optimizing_for_humans/01_not_so_simple.go
================================================
package humans
import (
"bytes"
"strconv"
"strings"
)
func NotSoSimple(ID int64, name string, age int, registered bool) string {
out := &bytes.Buffer{}
out.WriteString(strconv.FormatInt(ID, 10))
out.WriteString("-")
out.WriteString(strings.Replace(name, " ", "_", -1))
out.WriteString("-")
out.WriteString(strconv.Itoa(age))
out.WriteString("-")
out.WriteString(strconv.FormatBool(registered))
return out.String()
}
================================================
FILE: ch03/01_optimizing_for_humans/02_start_simple.go
================================================
package humans
import (
"fmt"
"strings"
)
func Simpler(ID int64, name string, age int, registered bool) string {
nameWithNoSpaces := strings.Replace(name, " ", "_", -1)
return fmt.Sprintf("%d-%s-%d-%t", ID, nameWithNoSpaces, age, registered)
}
================================================
FILE: ch03/01_optimizing_for_humans/03_too_abstract.go
================================================
package humans
import (
"io/ioutil"
"net/http"
)
type myGetter interface {
Get(url string) (*http.Response, error)
}
func TooAbstract(getter myGetter, url string) ([]byte, error) {
resp, err := getter.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
================================================
FILE: ch03/01_optimizing_for_humans/04_common_concept.go
================================================
package humans
import (
"io/ioutil"
"net/http"
)
func CommonConcept(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
================================================
FILE: ch03/01_optimizing_for_humans/05_boolean_param.go
================================================
package humans
import (
"time"
)
type Pet struct {
Name string
Dog bool
Born time.Time
}
func NewPet(name string, isDog bool) Pet {
return Pet{
Name: name,
Dog: isDog,
Born: time.Now(),
}
}
func CreatePetsV1() {
NewPet("Fido", true)
}
================================================
FILE: ch03/01_optimizing_for_humans/06_hidden_boolean.go
================================================
package humans
const (
isDog = true
isCat = false
)
func NewDog(name string) Pet {
return NewPet(name, isDog)
}
func NewCat(name string) Pet {
return NewPet(name, isCat)
}
func CreatePetsV2() {
NewDog("Fido")
}
================================================
FILE: ch03/01_optimizing_for_humans/07_wide_formatter.go
================================================
package humans
type WideFormatter interface {
ToCSV(pets []Pet) ([]byte, error)
ToGOB(pets []Pet) ([]byte, error)
ToJSON(pets []Pet) ([]byte, error)
}
================================================
FILE: ch03/01_optimizing_for_humans/08_thin_formatters.go
================================================
package humans
type ThinFormatter interface {
Format(pets []Pet) ([]byte, error)
}
type CSVFormatter struct{}
func (f CSVFormatter) Format(pets []Pet) ([]byte, error) {
// convert slice of pets to CSV
return nil, nil
}
type GOBFormatter struct{}
func (f GOBFormatter) Format(pets []Pet) ([]byte, error) {
// convert slice of pets to GOB
return nil, nil
}
type JSONFormatter struct{}
func (f JSONFormatter) Format(pets []Pet) ([]byte, error) {
// convert slice of pets to JSON
return nil, nil
}
================================================
FILE: ch03/01_optimizing_for_humans/09_extra_config.go
================================================
package humans
// PetFetcher searches the data store for pets whos name matches the search string.
// Limit is optional (default is 100). Offset is optional (default 0).
// sortBy is optional (default name). sortAscending is optional
func PetFetcher(search string, limit int, offset int, sortBy string, sortAscending bool) []Pet {
return []Pet{}
}
func PetFetcherTypicalUsage() {
_ = PetFetcher("Fido", 0, 0, "", true)
}
================================================
FILE: ch03/02_unit_tests/01_loader.go
================================================
package unit_tests
import (
"bytes"
"errors"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/assert"
)
type Loader interface {
Load(ID int) (*Pet, error)
}
func TestLoadAndPrint_happyPath(t *testing.T) {
result := &bytes.Buffer{}
LoadAndPrint(&happyPathLoader{}, 1, result)
assert.Contains(t, result.String(), "Pet named")
}
func TestLoadAndPrint_notFound(t *testing.T) {
result := &bytes.Buffer{}
LoadAndPrint(&missingLoader{}, 1, result)
assert.Contains(t, result.String(), "no such pet")
}
func TestLoadAndPrint_error(t *testing.T) {
result := &bytes.Buffer{}
LoadAndPrint(&errorLoader{}, 1, result)
assert.Contains(t, result.String(), "failed to load")
}
func LoadAndPrint(loader Loader, ID int, dest io.Writer) {
loadedPet, err := loader.Load(ID)
if err != nil {
fmt.Fprintf(dest, "failed to load pet with ID %d with error: %s", ID, err)
return
}
if loadedPet == nil {
fmt.Fprintf(dest, "no such pet found")
return
}
fmt.Fprintf(dest, "Pet named %s loaded", loadedPet.Name)
}
// implements Loader
type happyPathLoader struct {
}
func (l *happyPathLoader) Load(ID int) (*Pet, error) {
return &Pet{}, nil
}
// implements Loader
type missingLoader struct {
}
func (l *missingLoader) Load(ID int) (*Pet, error) {
return nil, nil
}
// implements Loader
type errorLoader struct {
}
func (l *errorLoader) Load(ID int) (*Pet, error) {
return nil, errors.New("failed")
}
================================================
FILE: ch03/02_unit_tests/02_language_feature.go
================================================
package unit_tests
import (
"testing"
"github.com/stretchr/testify/assert"
)
type Pet struct {
Name string
}
func NewPet(name string) *Pet {
return &Pet{
Name: name,
}
}
func TestLanguageFeatures(t *testing.T) {
petFish := NewPet("Goldie")
assert.IsType(t, &Pet{}, petFish)
}
================================================
FILE: ch03/02_unit_tests/03_simple_test.go
================================================
package unit_tests
import (
"testing"
"github.com/stretchr/testify/assert"
)
func concat(a, b string) string {
return a + b
}
func TestTooSimple(t *testing.T) {
a := "Hello "
b := "World"
expected := "Hello World"
assert.Equal(t, expected, concat(a, b))
}
================================================
FILE: ch03/02_unit_tests/04_test_from_api.go
================================================
package unit_tests
import (
"database/sql"
)
type PetSaver struct{}
// save the supplied pet and return the ID
func (p PetSaver) Save(pet Pet) (int, error) {
err := p.validate(pet)
if err != nil {
return 0, err
}
result, err := p.save(pet)
if err != nil {
return 0, err
}
return p.extractID(result)
}
// ensure the pet record is complete
func (p PetSaver) validate(pet Pet) error {
return nil
}
// save to the datastore
func (p PetSaver) save(pet Pet) (sql.Result, error) {
return nil, nil
}
// extract the ID from the result
func (p PetSaver) extractID(result sql.Result) (int, error) {
return 0, nil
}
================================================
FILE: ch03/02_unit_tests/05_repeated_code.go
================================================
package unit_tests
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Round the supplied number to the nearest integer
func Round(in float64) int {
return 0
}
func TestRound_down(t *testing.T) {
in := float64(1.1)
expected := 1
result := Round(in)
assert.Equal(t, expected, result)
}
func TestRound_up(t *testing.T) {
in := float64(3.7)
expected := 4
result := Round(in)
assert.Equal(t, expected, result)
}
func TestRound_noChange(t *testing.T) {
in := float64(6.0)
expected := 6
result := Round(in)
assert.Equal(t, expected, result)
}
================================================
FILE: ch03/02_unit_tests/06_tdt.go
================================================
package unit_tests
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRound(t *testing.T) {
scenarios := []struct {
desc string
in float64
expected int
}{
{
desc: "round down",
in: 1.1,
expected: 1,
},
{
desc: "round up",
in: 3.7,
expected: 4,
},
{
desc: "unchanged",
in: 6.0,
expected: 6,
},
}
for _, scenario := range scenarios {
in := scenario.in
result := Round(in)
assert.Equal(t, scenario.expected, result)
}
}
================================================
FILE: ch03/02_unit_tests/07_person_loader.go
================================================
package unit_tests
import (
"errors"
)
var ErrNotFound = errors.New("person not found")
type Person struct {
Name string
}
//go:generate mockery -name PersonLoader -testonly -inpkg -case=underscore
type PersonLoader interface {
Load(ID int) (*Person, error)
}
func LoadPersonName(loader PersonLoader, ID int) (string, error) {
person, err := loader.Load(ID)
if err != nil {
return "", err
}
return person.Name, nil
}
================================================
FILE: ch03/02_unit_tests/08_stub.go
================================================
package unit_tests
// Stubbed implementation of PersonLoader
type PersonLoaderStub struct {
Person *Person
Error error
}
func (p *PersonLoaderStub) Load(ID int) (*Person, error) {
return p.Person, p.Error
}
================================================
FILE: ch03/02_unit_tests/09_stub_tdt.go
================================================
package unit_tests
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLoadPersonNameStubs(t *testing.T) {
// this value does not matter as the stub ignores it
fakeID := 1
scenarios := []struct {
desc string
loaderStub *PersonLoaderStub
expectedName string
expectErr bool
}{
{
desc: "happy path",
loaderStub: &PersonLoaderStub{
Person: &Person{Name: "Sophia"},
},
expectedName: "Sophia",
expectErr: false,
},
{
desc: "input error",
loaderStub: &PersonLoaderStub{
Error: ErrNotFound,
},
expectedName: "",
expectErr: true,
},
{
desc: "system error path",
loaderStub: &PersonLoaderStub{
Error: errors.New("something failed"),
},
expectedName: "",
expectErr: true,
},
}
for _, scenario := range scenarios {
result, resultErr := LoadPersonName(scenario.loaderStub, fakeID)
assert.Equal(t, scenario.expectedName, result, scenario.desc)
assert.Equal(t, scenario.expectErr, resultErr != nil, scenario.desc)
}
}
================================================
FILE: ch03/02_unit_tests/10_mocks.go
================================================
package unit_tests
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func TestLoadPersonName(t *testing.T) {
// this value does not matter as the stub ignores it
fakeID := 1
scenarios := []struct {
desc string
configureMock func(stub *PersonLoaderMock)
expectedName string
expectErr bool
}{
{
desc: "happy path",
configureMock: func(loaderMock *PersonLoaderMock) {
loaderMock.On("Load", mock.Anything).
Return(&Person{Name: "Sophia"}, nil).
Once()
},
expectedName: "Sophia",
expectErr: false,
},
{
desc: "input error",
configureMock: func(loaderMock *PersonLoaderMock) {
loaderMock.On("Load", mock.Anything).
Return(nil, ErrNotFound).
Once()
},
expectedName: "",
expectErr: true,
},
{
desc: "system error path",
configureMock: func(loaderMock *PersonLoaderMock) {
loaderMock.On("Load", mock.Anything).
Return(nil, errors.New("something failed")).
Once()
},
expectedName: "",
expectErr: true,
},
}
for _, scenario := range scenarios {
mockLoader := &PersonLoaderMock{}
scenario.configureMock(mockLoader)
result, resultErr := LoadPersonName(mockLoader, fakeID)
assert.Equal(t, scenario.expectedName, result, scenario.desc)
assert.Equal(t, scenario.expectErr, resultErr != nil, scenario.desc)
assert.True(t, mockLoader.AssertExpectations(t), scenario.desc)
}
}
// Mocked implementation of PersonLoader
type PersonLoaderMock struct {
mock.Mock
}
func (p *PersonLoaderMock) Load(ID int) (*Person, error) {
outputs := p.Mock.Called(ID)
person := outputs.Get(0)
err := outputs.Error(1)
if person != nil {
return person.(*Person), err
}
return nil, err
}
================================================
FILE: ch03/03_test_induced_damage/01_io_closer.go
================================================
package test_damage
import (
"io"
)
func WriteAndClose(destination io.WriteCloser, contents string) error {
defer destination.Close()
_, err := destination.Write([]byte(contents))
if err != nil {
return err
}
return nil
}
================================================
FILE: ch03/03_test_induced_damage/02_json.go
================================================
package test_damage
import (
"encoding/json"
"io"
)
func PrintAsJSON(destination io.Writer, plant Plant) error {
bytes, err := json.Marshal(plant)
if err != nil {
return err
}
destination.Write(bytes)
return nil
}
type Plant struct {
Name string
}
================================================
FILE: ch03/04_visualizing_dependencies/depgraph.sh
================================================
#!/usr/bin/env bash
# Note:
# This script should be run in the base directory of the project/service
# Inputs
#
# This cuts down on typing by allowing you to enter only the sub-directory you wish to graph; instead of the entire
# package
prefix="./"
PKG=${1#$prefix}
# Constants
#
# Save the file on the desktop (so it's easy to find)
DEST_FILE=~/Desktop/depgraph.png
# Calculate the package in the current directory and assume this is the base or project package
BASE_PKG=$(go list)
EXCLUSIONS="$BASE_PKG/vendor"
BASE_PKG_DELIMITED=$(echo $BASE_PKG | sed 's/\//\\\//g')
# Generate
godepgraph -s \
-o "$BASE_PKG" \
-p "$EXCLUSIONS" \
$BASE_PKG/${PKG} |
sed "s/$BASE_PKG_DELIMITED//g" | dot -Tpng -o $DEST_FILE
# Open
open $DEST_FILE
================================================
FILE: ch03/fake.go
================================================
package Hands_On_Dependency_Injection_in_Go
func init() {
// This file is included so that Go tools (like `go list`) will find Go code in this directory and not error
}
================================================
FILE: ch04/01_welcome/01_bad_names.go
================================================
package welcome
type HouseV1 struct {
a string
b int
t int
p float64
}
================================================
FILE: ch04/01_welcome/02_improved_names.go
================================================
package welcome
type HouseV2 struct {
address string
bedrooms int
toilets int
price float64
}
================================================
FILE: ch04/01_welcome/03_long_method.go
================================================
package welcome
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
)
func longMethod(resp http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
userID, err := strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
if err != nil {
resp.WriteHeader(http.StatusPreconditionFailed)
return
}
row := DB.QueryRow("SELECT * FROM people WHERE ID = ?", userID)
person := &Person{}
err = row.Scan(&person.ID, &person.Name, &person.Phone)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
encoder := json.NewEncoder(resp)
err = encoder.Encode(person)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
}
var DB *sql.DB
type Person struct {
ID int64
Name string
Phone string
}
================================================
FILE: ch04/01_welcome/04_long_method_test.go
================================================
package welcome
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLongMethod_happyPath(t *testing.T) {
// build request
request := &http.Request{}
request.PostForm = url.Values{}
request.PostForm.Add("UserID", "123")
// mock the database
var mockDB sqlmock.Sqlmock
var err error
DB, mockDB, err = sqlmock.New()
require.NoError(t, err)
mockDB.ExpectQuery("SELECT .* FROM people WHERE ID = ?").
WithArgs(123).
WillReturnRows(sqlmock.NewRows([]string{"ID", "Name", "Phone"}).AddRow(123, "May", "0123456789"))
// build response
response := httptest.NewRecorder()
// call method
longMethod(response, request)
// validate response
require.Equal(t, http.StatusOK, response.Code)
// validate the JSON
responseBytes, err := ioutil.ReadAll(response.Body)
require.NoError(t, err)
expectedJSON := `{"ID":123,"Name":"May","Phone":"0123456789"}` + "\n"
assert.Equal(t, expectedJSON, string(responseBytes))
}
================================================
FILE: ch04/01_welcome/05_short_methods.go
================================================
package welcome
import (
"encoding/json"
"net/http"
"strconv"
)
func shortMethods(resp http.ResponseWriter, req *http.Request) {
userID, err := extractUserID(req)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
person, err := loadPerson(userID)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
outputPerson(resp, person)
}
func extractUserID(req *http.Request) (int64, error) {
err := req.ParseForm()
if err != nil {
return 0, err
}
return strconv.ParseInt(req.Form.Get("UserID"), 10, 64)
}
func loadPerson(userID int64) (*Person, error) {
row := DB.QueryRow("SELECT * FROM people WHERE ID = ?", userID)
person := &Person{}
err := row.Scan(&person.ID, &person.Name, &person.Phone)
if err != nil {
return nil, err
}
return person, nil
}
func outputPerson(resp http.ResponseWriter, person *Person) {
encoder := json.NewEncoder(resp)
err := encoder.Encode(person)
if err != nil {
resp.WriteHeader(http.StatusInternalServerError)
return
}
}
================================================
FILE: ch04/03_known_issues/01_data_and_rest/get_example.go
================================================
//+build ignore
package data_and_rest
import (
"encoding/json"
"io"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
)
// output the supplied person as JSON
func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) error {
// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
return json.NewEncoder(writer).Encode(person)
}
================================================
FILE: ch04/03_known_issues/02_config_coupling/config.go
================================================
package config_coupling
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/03_known_issues/02_config_coupling/currency"
)
type Config struct {
DefaultCurrency currency.Currency `json:"default_currency"`
}
================================================
FILE: ch04/03_known_issues/02_config_coupling/currency/currency.go
================================================
package currency
import (
"encoding/json"
"fmt"
)
// Currency is a custom type; used for convenience and code readability
type Currency string
// UnmarshalJSON implements json.Unmarshaler
func (c *Currency) UnmarshalJSON(in []byte) error {
var s string
err := json.Unmarshal(in, &s)
if err != nil {
return err
}
currency, valid := validCurrencies[s]
if !valid {
return fmt.Errorf("'%s' is not a valid currency", s)
}
*c = currency
return nil
}
const (
AUD = Currency("AUD")
CNY = Currency("CNY")
EUR = Currency("EUR")
USD = Currency("USD")
)
// a map of valid currencies
var validCurrencies = map[string]Currency{
string(AUD): AUD,
string(CNY): CNY,
string(EUR): EUR,
string(USD): USD,
}
================================================
FILE: ch04/acme/internal/config/config.go
================================================
package config
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
)
// DefaultEnvVar is the default environment variable the points to the config file
const DefaultEnvVar = "ACME_CONFIG"
// App is the application config
var App *Config
// Config defines the JSON format for the config file
type Config struct {
// DSN is the data source name (format: https://github.com/go-sql-driver/mysql/#dsn-data-source-name)
DSN string
// Address is the IP address and port to bind this rest to
Address string
// BasePrice is the price of registration
BasePrice float64
// ExchangeRateBaseURL is the server and protocol part of the URL from which to load the exchange rate
ExchangeRateBaseURL string
// ExchangeRateAPIKey is the API for the exchange rate API
ExchangeRateAPIKey string
}
// Load returns the config loaded from environment
func init() {
filename, found := os.LookupEnv(DefaultEnvVar)
if !found {
logging.L.Error("failed to locate file specified by %s", DefaultEnvVar)
return
}
_ = load(filename)
}
func load(filename string) error {
App = &Config{}
bytes, err := ioutil.ReadFile(filename)
if err != nil {
logging.L.Error("failed to read config file. err: %s", err)
return err
}
err = json.Unmarshal(bytes, App)
if err != nil {
logging.L.Error("failed to parse config file. err : %s", err)
return err
}
return nil
}
================================================
FILE: ch04/acme/internal/config/config_test.go
================================================
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoad(t *testing.T) {
scenarios := []struct {
desc string
in string
expectedConfig *Config
expectError bool
}{
{
desc: "happy path",
in: "../../../../default-config.json",
expectedConfig: &Config{
DSN: "[insert your db config here]",
Address: "0.0.0.0:8080",
BasePrice: 100.00,
ExchangeRateBaseURL: "http://apilayer.net",
ExchangeRateAPIKey: "[insert your API key here]",
},
expectError: false,
},
{
desc: "invalid path",
in: "invalid.json",
expectedConfig: &Config{},
expectError: true,
},
}
for _, s := range scenarios {
scenario := s
t.Run(scenario.desc, func(t *testing.T) {
resultErr := load(scenario.in)
require.Equal(t, scenario.expectError, resultErr != nil, "err: %s", resultErr)
assert.Equal(t, scenario.expectedConfig, App, scenario.desc)
})
}
}
================================================
FILE: ch04/acme/internal/logging/logging.go
================================================
package logging
import (
"fmt"
)
// L is the global instance of the logger
var L = &LoggerStdOut{}
// LoggerStdOut logs to std out
type LoggerStdOut struct{}
// Debug logs messages at DEBUG level
func (l LoggerStdOut) Debug(message string, args ...interface{}) {
fmt.Printf("[DEBUG] "+message, args...)
}
// Info logs messages at INFO level
func (l LoggerStdOut) Info(message string, args ...interface{}) {
fmt.Printf("[INFO] "+message, args...)
}
// Warn logs messages at WARN level
func (l LoggerStdOut) Warn(message string, args ...interface{}) {
fmt.Printf("[WARN] "+message, args...)
}
// Error logs messages at ERROR level
func (l LoggerStdOut) Error(message string, args ...interface{}) {
fmt.Printf("[ERROR] "+message, args...)
}
================================================
FILE: ch04/acme/internal/modules/data/data.go
================================================
package data
import (
"database/sql"
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
// import the MySQL Driver
_ "github.com/go-sql-driver/mysql"
)
const (
// default person id (returned on error)
defaultPersonID = 0
)
var (
db *sql.DB
// ErrNotFound is returned when the no records where matched by the query
ErrNotFound = errors.New("not found")
)
func getDB() (*sql.DB, error) {
if db == nil {
if config.App == nil {
return nil, errors.New("config is not initialized")
}
var err error
db, err = sql.Open("mysql", config.App.DSN)
if err != nil {
// if the DB cannot be accessed we are dead
panic(err.Error())
}
}
return db, nil
}
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
// Save will save the supplied person and return the ID of the newly created person or an error.
// Errors returned are caused by the underlying database or our connection to it.
func Save(in *Person) (int, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return defaultPersonID, err
}
// perform DB insert
query := "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
result, err := db.Exec(query, in.FullName, in.Phone, in.Currency, in.Price)
if err != nil {
logging.L.Error("failed to save person into DB. err: %s", err)
return defaultPersonID, err
}
// retrieve and return the ID of the person created
id, err := result.LastInsertId()
if err != nil {
logging.L.Error("failed to retrieve id of last saved person. err: %s", err)
return defaultPersonID, err
}
return int(id), nil
}
// LoadAll will attempt to load all people in the database
// It will return ErrNotFound when there are not people in the database
// Any other errors returned are caused by the underlying database or our connection to it.
func LoadAll() ([]*Person, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return nil, err
}
// perform DB select
query := "SELECT id, fullname, phone, currency, price FROM person"
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
var out []*Person
for rows.Next() {
// retrieve columns and populate the person object
record, err := populatePerson(rows.Scan)
if err != nil {
logging.L.Error("failed to convert query result. err: %s", err)
return nil, err
}
out = append(out, record)
}
if len(out) == 0 {
logging.L.Warn("no people found in the database.")
return nil, ErrNotFound
}
return out, nil
}
// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return nil, err
}
// perform DB select
query := "SELECT id, fullname, phone, currency, price FROM person WHERE id = ? LIMIT 1"
row := db.QueryRow(query, ID)
// retrieve columns and populate the person object
out, err := populatePerson(row.Scan)
if err != nil {
if err == sql.ErrNoRows {
logging.L.Warn("failed to load requested person '%d'. err: %s", ID, err)
return nil, ErrNotFound
}
logging.L.Error("failed to convert query result. err: %s", err)
return nil, err
}
return out, nil
}
// custom type so we can convert sql results to easily
type scanner func(dest ...interface{}) error
// reduce the duplication (and maintenance) between sql.Row and sql.Rows usage
func populatePerson(scanner scanner) (*Person, error) {
out := &Person{}
err := scanner(&out.ID, &out.FullName, &out.Phone, &out.Currency, &out.Price)
return out, err
}
================================================
FILE: ch04/acme/internal/modules/data/data_test.go
================================================
package data
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestData_happyPath(t *testing.T) {
in := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// save
resultID, err := Save(in)
require.Nil(t, err)
assert.True(t, resultID > 0)
// load
returned, err := Load(resultID)
require.NoError(t, err)
in.ID = resultID
assert.Equal(t, in, returned)
// load all
all, err := LoadAll()
require.NoError(t, err)
assert.True(t, len(all) > 0)
}
================================================
FILE: ch04/acme/internal/modules/exchange/converter.go
================================================
package exchange
import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
)
const (
// request URL for the exchange rate API
urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
// default price that is sent when an error occurs
defaultPrice = 0.0
)
// Converter will convert the base price to the currency supplied
// Note: we are expecting sane inputs and therefore skipping input validation
type Converter struct{}
// Do will perform the conversion
func (c *Converter) Do(basePrice float64, currency string) (float64, error) {
// load rate from the external API
response, err := c.loadRateFromServer(currency)
if err != nil {
return defaultPrice, err
}
// extract rate from response
rate, err := c.extractRate(response, currency)
if err != nil {
return defaultPrice, err
}
// apply rate and round to 2 decimal places
return math.Floor((basePrice/rate)*100) / 100, nil
}
// load rate from the external API
func (c *Converter) loadRateFromServer(currency string) (*http.Response, error) {
// build the request
url := fmt.Sprintf(urlFormat,
config.App.ExchangeRateBaseURL,
config.App.ExchangeRateAPIKey,
currency)
// perform request
response, err := http.Get(url)
if err != nil {
logging.L.Warn("[exchange] failed to load. err: %s", err)
return nil, err
}
if response.StatusCode != http.StatusOK {
err = fmt.Errorf("request failed with code %d", response.StatusCode)
logging.L.Warn("[exchange] %s", err)
return nil, err
}
return response, nil
}
func (c *Converter) extractRate(response *http.Response, currency string) (float64, error) {
defer func() {
_ = response.Body.Close()
}()
// extract data from response
data, err := c.extractResponse(response)
if err != nil {
return defaultPrice, err
}
// pull rate from response data
rate, found := data.Quotes["USD"+currency]
if !found {
err = fmt.Errorf("response did not include expected currency '%s'", currency)
logging.L.Error("[exchange] %s", err)
return defaultPrice, err
}
// happy path
return rate, nil
}
func (c *Converter) extractResponse(response *http.Response) (*apiResponseFormat, error) {
payload, err := ioutil.ReadAll(response.Body)
if err != nil {
logging.L.Error("[exchange] failed to ready response body. err: %s", err)
return nil, err
}
data := &apiResponseFormat{}
err = json.Unmarshal(payload, data)
if err != nil {
logging.L.Error("[exchange] error converting response. err: %s", err)
return nil, err
}
// happy path
return data, nil
}
// the response format from the exchange rate API
type apiResponseFormat struct {
Quotes map[string]float64 `json:"quotes"`
}
================================================
FILE: ch04/acme/internal/modules/get/get.go
================================================
package get
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
)
var (
// error thrown when the requested person is not in the database
errPersonNotFound = errors.New("person not found")
)
// Getter will attempt to load a person.
// It can return an error caused by the data layer or when the requested person is not found
type Getter struct {
}
// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
// load person from the data layer
person, err := data.Load(ID)
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are encapsulating the implementation details from our users.
return nil, errPersonNotFound
}
return nil, err
}
return person, err
}
================================================
FILE: ch04/acme/internal/modules/get/go_test.go
================================================
package get
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetter_Do(t *testing.T) {
// inputs
ID := 1
name := "John"
// call method
getter := &Getter{}
person, err := getter.Do(ID)
// validate expectations
require.NoError(t, err)
assert.Equal(t, ID, person.ID)
assert.Equal(t, name, person.FullName)
}
================================================
FILE: ch04/acme/internal/modules/list/list.go
================================================
package list
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
)
var (
// error thrown when there are no people in the database
errPeopleNotFound = errors.New("no people found")
)
// Lister will attempt to load all people in the database.
// It can return an error caused by the data layer
type Lister struct {
}
// Do will load the people from the data layer
func (l *Lister) Do() ([]*data.Person, error) {
// load all people
people, err := l.load()
if err != nil {
return nil, err
}
if len(people) == 0 {
// special processing for 0 people returned
return nil, errPeopleNotFound
}
return people, nil
}
// load all people
func (l *Lister) load() ([]*data.Person, error) {
people, err := data.LoadAll()
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are encapsulating the implementation details from our users.
return nil, errPeopleNotFound
}
return nil, err
}
return people, nil
}
================================================
FILE: ch04/acme/internal/modules/list/list_test.go
================================================
package list
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLister_Do(t *testing.T) {
// call method
lister := &Lister{}
persons, err := lister.load()
// validate expectations
require.NoError(t, err)
assert.True(t, len(persons) >= 4)
}
================================================
FILE: ch04/acme/internal/modules/register/register.go
================================================
package register
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/exchange"
)
const (
// default person id (returned on error)
defaultPersonID = 0
)
var (
// validation errors
errNameMissing = errors.New("name is missing")
errPhoneMissing = errors.New("phone is missing")
errCurrencyMissing = errors.New("currency is missing")
errInvalidCurrency = errors.New("currency is invalid, supported types are AUD, CNY, EUR, GBP, JPY, MYR, SGD, USD")
// a little trick to make checking for supported currencies easier
supportedCurrencies = map[string]struct{}{
"AUD": {},
"CNY": {},
"EUR": {},
"GBP": {},
"JPY": {},
"MYR": {},
"SGD": {},
"USD": {},
}
)
// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}
// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
// validate the request
err := r.validateInput(in)
if err != nil {
logging.L.Warn("input validation failed with err: %s", err)
return defaultPersonID, err
}
// get price in the requested currency
price, err := r.getPrice(in.Currency)
if err != nil {
return defaultPersonID, err
}
// save registration
id, err := r.save(in, price)
if err != nil {
// no need to log here as we expect the data layer to do so
return defaultPersonID, err
}
return id, nil
}
// validate input and return error on fail
func (r *Registerer) validateInput(in *data.Person) error {
if in.FullName == "" {
return errNameMissing
}
if in.Phone == "" {
return errPhoneMissing
}
if in.Currency == "" {
return errCurrencyMissing
}
if _, found := supportedCurrencies[in.Currency]; !found {
return errInvalidCurrency
}
// happy path
return nil
}
// get price in the requested currency
func (r *Registerer) getPrice(currency string) (float64, error) {
converter := &exchange.Converter{}
price, err := converter.Do(config.App.BasePrice, currency)
if err != nil {
logging.L.Warn("failed to convert the price. err: %s", err)
return defaultPersonID, err
}
return price, nil
}
// save the registration
func (r *Registerer) save(in *data.Person, price float64) (int, error) {
person := &data.Person{
FullName: in.FullName,
Phone: in.Phone,
Currency: in.Currency,
Price: price,
}
return data.Save(person)
}
================================================
FILE: ch04/acme/internal/modules/register/register_test.go
================================================
package register
import (
"testing"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRegisterer_Do(t *testing.T) {
// inputs
in := &data.Person{
FullName: "Chang",
Phone: "11122233345",
Currency: "CNY",
}
// call method
registerer := &Registerer{}
ID, err := registerer.Do(in)
// validate expectations
require.NoError(t, err)
assert.True(t, ID > 0)
}
================================================
FILE: ch04/acme/internal/rest/common_test.go
================================================
package rest
import (
"context"
"net"
)
func getOpenPort() (string, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", err
}
address := listener.Addr().String()
listener.Close()
return address, nil
}
func startServer(ctx context.Context) (string, error) {
// get open port
address, err := getOpenPort()
if err != nil {
return "", err
}
// start a server
server := New(address)
go server.Listen(ctx.Done())
// wait for server to be ready
dialer := &net.Dialer{}
for {
conn, _ := dialer.DialContext(ctx, "tcp", address)
if conn != nil {
defer conn.Close()
return address, nil
}
select {
case <-ctx.Done():
return "", ctx.Err()
default:
// try again
}
}
return address, nil
}
================================================
FILE: ch04/acme/internal/rest/get.go
================================================
package rest
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/logging"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/get"
"github.com/gorilla/mux"
)
const (
// default person id (returned on error)
defaultPersonID = 0
)
// GetHandler is the HTTP handler for the "Get Person" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400
// or "not found" HTTP 404
// There are some programmer errors possible but hopefully these will be caught in testing.
type GetHandler struct {
}
// ServeHTTP implements http.Handler
func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract person id from request
id, err := h.extractID(request)
if err != nil {
// output error
response.WriteHeader(http.StatusBadRequest)
return
}
// attempt get
getter := get.Getter{}
person, err := getter.Do(id)
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusNotFound)
return
}
// happy path
err = h.writeJSON(response, person)
if err != nil {
// this error should not happen but if it does there is nothing we can do to recover
response.WriteHeader(http.StatusInternalServerError)
}
}
// extract the person ID from the request
func (h *GetHandler) extractID(request *http.Request) (int, error) {
// ID is part of the URL, so we extract it from there
vars := mux.Vars(request)
idAsString, exists := vars["id"]
if !exists {
// log and return error
err := errors.New("[get] person id missing from request")
logging.L.Warn(err.Error())
return defaultPersonID, err
}
// convert ID to int
id, err := strconv.Atoi(idAsString)
if err != nil {
// log and return error
err = fmt.Errorf("[get] failed to convert person id into a number. err: %s", err)
logging.L.Error(err.Error())
return defaultPersonID, err
}
return id, nil
}
// output the supplied person as JSON
func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) error {
output := &getResponseFormat{
ID: person.ID,
FullName: person.FullName,
Phone: person.Phone,
Currency: person.Currency,
Price: person.Price,
}
// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
return json.NewEncoder(writer).Encode(output)
}
// the JSON response format
type getResponseFormat struct {
ID int `json:"id"`
FullName string `json:"name"`
Phone string `json:"phone"`
Currency string `json:"currency"`
Price float64 `json:"price"`
}
================================================
FILE: ch04/acme/internal/rest/get_test.go
================================================
package rest
import (
"context"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
response, err := http.Get("http://" + address + "/person/1/")
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
expectedPayload := []byte(`{"id":1,"name":"John","phone":"0123456780","currency":"USD","price":100}` + "\n")
payload, _ := ioutil.ReadAll(response.Body)
defer response.Body.Close()
assert.Equal(t, expectedPayload, payload)
}
================================================
FILE: ch04/acme/internal/rest/list.go
================================================
package rest
import (
"encoding/json"
"io"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/list"
)
// ListHandler is the HTTP handler for the "List Do people" endpoint
// In this simplified example we are assuming all possible errors are system errors (HTTP 500)
type ListHandler struct {
}
// ServeHTTP implements http.Handler
func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// attempt loadAll
lister := list.Lister{}
people, err := lister.Do()
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusNotFound)
return
}
// happy path
err = h.writeJSON(response, people)
if err != nil {
// this error should not happen but if it does there is nothing we can do to recover
response.WriteHeader(http.StatusInternalServerError)
}
}
// output the result as JSON
func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Person) error {
output := &listResponseFormat{
People: make([]*listResponseItemFormat, len(people)),
}
for index, record := range people {
output.People[index] = &listResponseItemFormat{
ID: record.ID,
FullName: record.FullName,
Phone: record.Phone,
}
}
// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
return json.NewEncoder(writer).Encode(output)
}
type listResponseFormat struct {
People []*listResponseItemFormat `json:"people"`
}
type listResponseItemFormat struct {
ID int `json:"id"`
FullName string `json:"name"`
Phone string `json:"phone"`
}
================================================
FILE: ch04/acme/internal/rest/list_test.go
================================================
package rest
import (
"context"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestListHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
response, err := http.Get("http://" + address + "/person/list")
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
expectedPayload := []byte(`{"people":[{"id":1,"name":"John","phone":"0123456780"},{"id":2,"name":"Paul","phone":"0123456781"},{"id":3,"name":"George","phone":"0123456782"},{"id":4,"name":"Ringo","phone":"0123456783"}`)
payload, _ := ioutil.ReadAll(response.Body)
defer response.Body.Close()
// we have to use contains because other tests add more records
assert.Contains(t, string(payload), string(expectedPayload))
}
================================================
FILE: ch04/acme/internal/rest/not_found.go
================================================
package rest
import (
"net/http"
)
func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
response.WriteHeader(http.StatusNotFound)
_, _ = response.Write([]byte(`Not found`))
}
================================================
FILE: ch04/acme/internal/rest/not_found_test.go
================================================
package rest
import (
"context"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestNotFoundHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
response, err := http.Get("http://" + address + "/some-bad-address")
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, response.StatusCode)
}
================================================
FILE: ch04/acme/internal/rest/register.go
================================================
package rest
import (
"encoding/json"
"fmt"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/modules/register"
)
// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
}
// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract payload from request
requestPayload, err := h.extractPayload(request)
if err != nil {
// output error
response.WriteHeader(http.StatusBadRequest)
return
}
// register person
id, err := h.register(requestPayload)
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusBadRequest)
return
}
// happy path
response.Header().Add("Location", fmt.Sprintf("/person/%d/", id))
response.WriteHeader(http.StatusCreated)
}
// extract payload from request
func (h *RegisterHandler) extractPayload(request *http.Request) (*registerRequest, error) {
requestPayload := ®isterRequest{}
decoder := json.NewDecoder(request.Body)
err := decoder.Decode(requestPayload)
if err != nil {
return nil, err
}
return requestPayload, nil
}
// call the logic layer
func (h *RegisterHandler) register(requestPayload *registerRequest) (int, error) {
person := &data.Person{
FullName: requestPayload.FullName,
Phone: requestPayload.Phone,
Currency: requestPayload.Currency,
}
registerer := ®ister.Registerer{}
return registerer.Do(person)
}
// register endpoint request format
type registerRequest struct {
// FullName of the person
FullName string `json:"fullName"`
// Phone of the person
Phone string `json:"phone"`
// Currency the wish to register in
Currency string `json:"currency"`
}
================================================
FILE: ch04/acme/internal/rest/register_test.go
================================================
package rest
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRegisterHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
validRequest := buildValidRequest()
response, err := http.Post("http://"+address+"/person/register", "application/json", validRequest)
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusCreated, response.StatusCode)
defer response.Body.Close()
// call should output the location to the new person
headerLocation := response.Header.Get("Location")
assert.Contains(t, headerLocation, "/person/")
}
func buildValidRequest() io.Reader {
requestData := ®isterRequest{
FullName: "Joan Smith",
Currency: "AUD",
Phone: "01234567890",
}
data, _ := json.Marshal(requestData)
return bytes.NewBuffer(data)
}
================================================
FILE: ch04/acme/internal/rest/server.go
================================================
package rest
import (
"net/http"
"github.com/gorilla/mux"
)
// New will create and initialize the server
func New(address string) *Server {
return &Server{
address: address,
handlerGet: &GetHandler{},
handlerList: &ListHandler{},
handlerNotFound: notFoundHandler,
handlerRegister: &RegisterHandler{},
}
}
// Server is the HTTP REST server
type Server struct {
address string
server *http.Server
handlerGet http.Handler
handlerList http.Handler
handlerNotFound http.HandlerFunc
handlerRegister http.Handler
}
// Listen will start a HTTP rest for this service
func (s *Server) Listen(stop <-chan struct{}) {
router := s.buildRouter()
// create the HTTP server
s.server = &http.Server{
Handler: router,
Addr: s.address,
}
// listen for shutdown
go func() {
// wait for shutdown signal
<-stop
_ = s.server.Close()
}()
// start the HTTP server
_ = s.server.ListenAndServe()
}
// configure the endpoints to handlers
func (s *Server) buildRouter() http.Handler {
router := mux.NewRouter()
// map URL endpoints to HTTP handlers
router.Handle("/person/{id}/", s.handlerGet).Methods("GET")
router.Handle("/person/list", s.handlerList).Methods("GET")
router.Handle("/person/register", s.handlerRegister).Methods("POST")
// convert a "catch all" not found handler
router.NotFoundHandler = s.handlerNotFound
return router
}
================================================
FILE: ch04/acme/main.go
================================================
package main
import (
"context"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/rest"
)
func main() {
// bind stop channel to context
ctx := context.Background()
// start REST server
server := rest.New(config.App.Address)
server.Listen(ctx.Done())
}
================================================
FILE: ch04/fake.go
================================================
package ch04
func init() {
// This file is included so that Go tools (like `go list`) will find Go code in this directory and not error
}
================================================
FILE: ch05/02_advantages/01_function.go
================================================
package advantages
import (
"encoding/json"
"io/ioutil"
"log"
)
func SaveConfig(filename string, cfg *Config) error {
// convert to JSON
data, err := json.Marshal(cfg)
if err != nil {
return err
}
// save file
err = ioutil.WriteFile(filename, data, 0666)
if err != nil {
log.Printf("failed to save file '%s' with err: %s", filename, err)
return err
}
return nil
}
type Config struct {
Host string
Port int
}
================================================
FILE: ch05/02_advantages/02_monkey_patched.go
================================================
package advantages
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
)
func SaveConfigPatched(filename string, cfg *Config) error {
// convert to JSON
data, err := json.Marshal(cfg)
if err != nil {
return err
}
// save file
err = writeFile(filename, data, 0666)
if err != nil {
log.Printf("failed to save file '%s' with err: %s", filename, err)
return err
}
return nil
}
// Custom type that allows us to Monkey Patch
var writeFile = ioutil.WriteFile
// Usage
func SaveConfigPatchedUsage() {
cfg := &Config{
// build the config
}
err := SaveConfigPatched("myfile.json", cfg)
if err != nil {
fmt.Printf("failed with err: %s", err)
}
}
================================================
FILE: ch05/02_advantages/03_injected_lambda.go
================================================
package advantages
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
)
func SaveConfigInjected(writer fileWriter, filename string, cfg *Config) error {
// convert to JSON
data, err := json.Marshal(cfg)
if err != nil {
return err
}
// save file
err = writer(filename, data, 0666)
if err != nil {
log.Printf("failed to save file '%s' with err: %s", filename, err)
return err
}
return nil
}
// This custom type is not strictly needed but it does make the function
// signature a little cleaner
type fileWriter func(filename string, data []byte, perm os.FileMode) error
// Usage
func SaveConfigInjectedUsage() {
cfg := &Config{
// build the config
}
err := SaveConfigInjected(ioutil.WriteFile, "myfile.json", cfg)
if err != nil {
fmt.Printf("failed with err: %s", err)
}
}
================================================
FILE: ch05/02_advantages/04_as_object.go
================================================
package advantages
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
)
type ConfigSaver struct {
FileWriter func(filename string, data []byte, perm os.FileMode) error
}
func (c ConfigSaver) Save(filename string, cfg *Config) error {
// convert to JSON
data, err := json.Marshal(cfg)
if err != nil {
return err
}
// save file
err = c.FileWriter(filename, data, 0666)
if err != nil {
log.Printf("failed to save file '%s' with err: %s", filename, err)
return err
}
return nil
}
// Usage
func ConfigSaverUsage() {
cfg := &Config{
// build the config
}
saver := &ConfigSaver{
FileWriter: ioutil.WriteFile,
}
err := saver.Save("myfile.json", cfg)
if err != nil {
fmt.Printf("failed with err: %s", err)
}
}
================================================
FILE: ch05/02_advantages/05_math_rand.go
================================================
package advantages
// A Rand is a source of random numbers.
type Rand struct {
src Source
// code removed
}
// Int returns a non-negative pseudo-random int.
func (r *Rand) Int() int {
// code changed for brevity
value := r.src.Int63()
return int(value)
}
/*
* Top-level convenience functions
*/
var globalRand = New(&lockedSource{})
// Int returns a non-negative pseudo-random int from the default Source.
func Int() int { return globalRand.Int() }
/*
* Code below here has been modified so that it compiles but does nothing.
* The original code is: https://golang.org/src/math/rand/rand.go
*/
// New returns a new Rand that uses random values from src
// to generate other random values.
func New(src Source) *Rand {
// code changed for brevity
return &Rand{
src: src,
}
}
type lockedSource struct {
// code removed
}
func (l *lockedSource) Int63() int64 {
// code removed
return 0
}
// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1<<63).
type Source interface {
Int63() int64
// code removed
}
================================================
FILE: ch05/02_advantages/06_math_rand_test.go
================================================
package advantages
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestInt(t *testing.T) {
// monkey patch
defer func(original *Rand) {
// restore patch after use
globalRand = original
}(globalRand)
// swap out for a predictable outcome
globalRand = New(&stubSource{})
// end monkey patch
// call the function
result := Int()
assert.Equal(t, 234, result)
}
// this is a stubbed implementation of Source that returns a predictable value
type stubSource struct {
}
func (s *stubSource) Int63() int64 {
return 234
}
================================================
FILE: ch05/03_applying/01_simple_sqlmock_test.go
================================================
package applying
import (
"database/sql"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSave_happyPath(t *testing.T) {
// define a mock db
testDb, dbMock, err := sqlmock.New()
require.NoError(t, err)
// clean up afterwards
defer testDb.Close()
// define the query we are expecting as regular expression
queryRegex := `\QINSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)\E`
// configure the mock db
dbMock.ExpectExec(queryRegex).WillReturnResult(sqlmock.NewResult(2, 1))
// inputs
person := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// call function
resultID, err := SavePerson(testDb, person)
// validate result
require.NoError(t, err)
assert.Equal(t, 2, resultID)
assert.NoError(t, dbMock.ExpectationsWereMet())
}
func SavePerson(db *sql.DB, in *Person) (int, error) {
// perform DB insert
query := "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
result, err := db.Exec(query, in.FullName, in.Phone, in.Currency, in.Price)
if err != nil {
return 0, err
}
// retrieve and return the ID of the person created
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(id), nil
}
================================================
FILE: ch05/03_applying/02_load.go
================================================
package applying
import (
"database/sql"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
// SQL statements as constants (to reduce duplication and maintenance in tests)
sqlAllColumns = "id, fullname, phone, currency, price"
sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? LIMIT 1"
)
func TestLoad_happyPath(t *testing.T) {
expectedResult := &Person{
ID: 2,
FullName: "Paul",
Phone: "0123456789",
Currency: "CAD",
Price: 23.45,
}
// define a mock db
testDb, dbMock, err := sqlmock.New()
require.NoError(t, err)
// configure the mock db
queryRegex := convertSQLToRegex(sqlLoadByID)
dbMock.ExpectQuery(queryRegex).WillReturnRows(
sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
AddRow(2, "Paul", "0123456789", "CAD", 23.45))
// monkey patching the database
defer func(original sql.DB) {
// restore original DB (after test)
db = &original
}(*db)
db = testDb
// end of monkey patch
// call function
result, err := Load(2)
// validate results
assert.Equal(t, expectedResult, result)
assert.NoError(t, err)
assert.NoError(t, dbMock.ExpectationsWereMet())
}
// convert SQL string to regex by treating the entire query as a literal
func convertSQLToRegex(in string) string {
return `\Q` + in + `\E`
}
// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
// code removed/faked for brevity
return &Person{
ID: 2,
FullName: "Paul",
Phone: "0123456789",
Currency: "CAD",
Price: 23.45,
}, nil
}
// code removed for brevity
var db = &sql.DB{}
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
================================================
FILE: ch05/04_disadvantages/01_verbose.go
================================================
package disadvantages
import (
"encoding/json"
"io/ioutil"
"log"
)
func SaveConfig(filename string, cfg *Config) error {
// convert to JSON
data, err := json.Marshal(cfg)
if err != nil {
return err
}
// save file
err = writeFile(filename, data, 0666)
if err != nil {
log.Printf("failed to save file '%s' with err: %s", filename, err)
return err
}
return nil
}
// Custom type that allows
var writeFile = ioutil.WriteFile
type Config struct {
Host string
Port int
}
================================================
FILE: ch05/04_disadvantages/02_verbose_test.go
================================================
package disadvantages
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSaveConfig(t *testing.T) {
// inputs
filename := "my-config.json"
cfg := &Config{
Host: "localhost",
Port: 1234,
}
// monkey patch the file writer
defer func(original func(filename string, data []byte, perm os.FileMode) error) {
// restore the original
writeFile = original
}(writeFile)
writeFile = func(filename string, data []byte, perm os.FileMode) error {
// output error
return nil
}
// call the function
err := SaveConfig(filename, cfg)
// validate the result
assert.NoError(t, err)
}
================================================
FILE: ch05/04_disadvantages/03_refactored_test.go
================================================
package disadvantages
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSaveConfig_refactored(t *testing.T) {
// inputs
filename := "my-config.json"
cfg := &Config{
Host: "localhost",
Port: 1234,
}
// monkey patch the file writer
defer restoreWriteFile(writeFile)
writeFile = mockWriteFile(nil)
// call the function
err := SaveConfig(filename, cfg)
// validate the result
assert.NoError(t, err)
}
func mockWriteFile(result error) func(filename string, data []byte, perm os.FileMode) error {
return func(filename string, data []byte, perm os.FileMode) error {
return result
}
}
// remove the restore function to reduce from 3 lines to 1
func restoreWriteFile(original func(filename string, data []byte, perm os.FileMode) error) {
// restore the original
writeFile = original
}
================================================
FILE: ch05/acme/internal/config/config.go
================================================
package config
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
)
// DefaultEnvVar is the default environment variable the points to the config file
const DefaultEnvVar = "ACME_CONFIG"
// App is the application config
var App *Config
// Config defines the JSON format for the config file
type Config struct {
// DSN is the data source name (format: https://github.com/go-sql-driver/mysql/#dsn-data-source-name)
DSN string
// Address is the IP address and port to bind this rest to
Address string
// BasePrice is the price of registration
BasePrice float64
// ExchangeRateBaseURL is the server and protocol part of the URL from which to load the exchange rate
ExchangeRateBaseURL string
// ExchangeRateAPIKey is the API for the exchange rate API
ExchangeRateAPIKey string
}
// Load returns the config loaded from environment
func init() {
filename, found := os.LookupEnv(DefaultEnvVar)
if !found {
logging.L.Error("failed to locate file specified by %s", DefaultEnvVar)
return
}
_ = load(filename)
}
func load(filename string) error {
App = &Config{}
bytes, err := ioutil.ReadFile(filename)
if err != nil {
logging.L.Error("failed to read config file. err: %s", err)
return err
}
err = json.Unmarshal(bytes, App)
if err != nil {
logging.L.Error("failed to parse config file. err : %s", err)
return err
}
return nil
}
================================================
FILE: ch05/acme/internal/config/config_test.go
================================================
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoad(t *testing.T) {
scenarios := []struct {
desc string
in string
expectedConfig *Config
expectError bool
}{
{
desc: "happy path",
in: "../../../../default-config.json",
expectedConfig: &Config{
DSN: "[insert your db config here]",
Address: "0.0.0.0:8080",
BasePrice: 100.00,
ExchangeRateBaseURL: "http://apilayer.net",
ExchangeRateAPIKey: "[insert your API key here]",
},
expectError: false,
},
{
desc: "invalid path",
in: "invalid.json",
expectedConfig: &Config{},
expectError: true,
},
}
for _, s := range scenarios {
scenario := s
t.Run(scenario.desc, func(t *testing.T) {
resultErr := load(scenario.in)
require.Equal(t, scenario.expectError, resultErr != nil, "err: %s", resultErr)
assert.Equal(t, scenario.expectedConfig, App, scenario.desc)
})
}
}
================================================
FILE: ch05/acme/internal/logging/logging.go
================================================
package logging
import (
"fmt"
)
// L is the global instance of the logger
var L = &LoggerStdOut{}
// LoggerStdOut logs to std out
type LoggerStdOut struct{}
// Debug logs messages at DEBUG level
func (l LoggerStdOut) Debug(message string, args ...interface{}) {
fmt.Printf("[DEBUG] "+message, args...)
}
// Info logs messages at INFO level
func (l LoggerStdOut) Info(message string, args ...interface{}) {
fmt.Printf("[INFO] "+message, args...)
}
// Warn logs messages at WARN level
func (l LoggerStdOut) Warn(message string, args ...interface{}) {
fmt.Printf("[WARN] "+message, args...)
}
// Error logs messages at ERROR level
func (l LoggerStdOut) Error(message string, args ...interface{}) {
fmt.Printf("[ERROR] "+message, args...)
}
================================================
FILE: ch05/acme/internal/modules/data/data.go
================================================
package data
import (
"database/sql"
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
// import the MySQL Driver
_ "github.com/go-sql-driver/mysql"
)
const (
// default person id (returned on error)
defaultPersonID = 0
// SQL statements as constants (to reduce duplication and maintenance in tests)
sqlAllColumns = "id, fullname, phone, currency, price"
sqlInsert = "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? LIMIT 1"
)
var (
db *sql.DB
// ErrNotFound is returned when the no records where matched by the query
ErrNotFound = errors.New("not found")
)
var getDB = func() (*sql.DB, error) {
if db == nil {
if config.App == nil {
return nil, errors.New("config is not initialized")
}
var err error
db, err = sql.Open("mysql", config.App.DSN)
if err != nil {
// if the DB cannot be accessed we are dead
panic(err.Error())
}
}
return db, nil
}
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
// Save will save the supplied person and return the ID of the newly created person or an error.
// Errors returned are caused by the underlying database or our connection to it.
func Save(in *Person) (int, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return defaultPersonID, err
}
// perform DB insert
result, err := db.Exec(sqlInsert, in.FullName, in.Phone, in.Currency, in.Price)
if err != nil {
logging.L.Error("failed to save person into DB. err: %s", err)
return defaultPersonID, err
}
// retrieve and return the ID of the person created
id, err := result.LastInsertId()
if err != nil {
logging.L.Error("failed to retrieve id of last saved person. err: %s", err)
return defaultPersonID, err
}
return int(id), nil
}
// LoadAll will attempt to load all people in the database
// It will return ErrNotFound when there are not people in the database
// Any other errors returned are caused by the underlying database or our connection to it.
func LoadAll() ([]*Person, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return nil, err
}
// perform DB select
rows, err := db.Query(sqlLoadAll)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
var out []*Person
for rows.Next() {
// retrieve columns and populate the person object
record, err := populatePerson(rows.Scan)
if err != nil {
logging.L.Error("failed to convert query result. err: %s", err)
return nil, err
}
out = append(out, record)
}
if len(out) == 0 {
logging.L.Warn("no people found in the database.")
return nil, ErrNotFound
}
return out, nil
}
// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return nil, err
}
// perform DB select
row := db.QueryRow(sqlLoadByID, ID)
// retrieve columns and populate the person object
out, err := populatePerson(row.Scan)
if err != nil {
if err == sql.ErrNoRows {
logging.L.Warn("failed to load requested person '%d'. err: %s", ID, err)
return nil, ErrNotFound
}
logging.L.Error("failed to convert query result. err: %s", err)
return nil, err
}
return out, nil
}
// custom type so we can convert sql results to easily
type scanner func(dest ...interface{}) error
// reduce the duplication (and maintenance) between sql.Row and sql.Rows usage
func populatePerson(scanner scanner) (*Person, error) {
out := &Person{}
err := scanner(&out.ID, &out.FullName, &out.Phone, &out.Currency, &out.Price)
return out, err
}
func init() {
// ensure the config is loaded and the db initialized
_, _ = getDB()
}
================================================
FILE: ch05/acme/internal/modules/data/data_test.go
================================================
package data
import (
"database/sql"
"errors"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSave_happyPath(t *testing.T) {
// define a mock db
testDb, dbMock, err := sqlmock.New()
defer testDb.Close()
require.NoError(t, err)
// configure the mock db
queryRegex := convertSQLToRegex(sqlInsert)
dbMock.ExpectExec(queryRegex).WillReturnResult(sqlmock.NewResult(2, 1))
// monkey patching starts here
defer func(original sql.DB) {
// restore original DB (after test)
db = &original
}(*db)
// replace db for this test
db = testDb
// end of monkey patch
// inputs
in := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// call function
resultID, err := Save(in)
// validate result
require.NoError(t, err)
assert.Equal(t, 2, resultID)
assert.NoError(t, dbMock.ExpectationsWereMet())
}
func TestSave_insertError(t *testing.T) {
// define a mock db
testDb, dbMock, err := sqlmock.New()
defer testDb.Close()
require.NoError(t, err)
// configure the mock db
queryRegex := convertSQLToRegex(sqlInsert)
dbMock.ExpectExec(queryRegex).WillReturnError(errors.New("failed to insert"))
// monkey patching starts here
defer func(original sql.DB) {
// restore original DB (after test)
db = &original
}(*db)
// replace db for this test
db = testDb
// end of monkey patch
// inputs
in := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// call function
resultID, err := Save(in)
// validate result
require.Error(t, err)
assert.Equal(t, defaultPersonID, resultID)
assert.NoError(t, dbMock.ExpectationsWereMet())
}
func TestSave_getDBError(t *testing.T) {
// monkey patching starts here
defer func(original func() (*sql.DB, error)) {
// restore original DB (after test)
getDB = original
}(getDB)
// replace getDB() function for this test
getDB = func() (*sql.DB, error) {
return nil, errors.New("getDB() failed")
}
// end of monkey patch
// inputs
in := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// call function
resultID, err := Save(in)
require.Error(t, err)
assert.Equal(t, defaultPersonID, resultID)
}
func TestLoadAll_tableDrivenTest(t *testing.T) {
scenarios := []struct {
desc string
configureMockDB func(sqlmock.Sqlmock)
expectedResults []*Person
expectError bool
}{
{
desc: "happy path",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnRows(
sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
AddRow(1, "John", "0123456789", "AUD", 12.34))
},
expectedResults: []*Person{
{
ID: 1,
FullName: "John",
Phone: "0123456789",
Currency: "AUD",
Price: 12.34,
},
},
expectError: false,
},
{
desc: "load error",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
},
expectedResults: nil,
expectError: true,
},
}
for _, scenario := range scenarios {
// define a mock db
testDb, dbMock, err := sqlmock.New()
require.NoError(t, err)
// configure the mock db
scenario.configureMockDB(dbMock)
// monkey patch the db for this test
original := *db
db = testDb
// call function
results, err := LoadAll()
// validate results
assert.Equal(t, scenario.expectedResults, results, scenario.desc)
assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
assert.NoError(t, dbMock.ExpectationsWereMet())
// restore original DB (after test)
db = &original
testDb.Close()
}
}
func TestLoad_tableDrivenTest(t *testing.T) {
scenarios := []struct {
desc string
configureMockDB func(sqlmock.Sqlmock)
expectedResult *Person
expectError bool
}{
{
desc: "happy path",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnRows(
sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
AddRow(2, "Paul", "0123456789", "CAD", 23.45))
},
expectedResult: &Person{
ID: 2,
FullName: "Paul",
Phone: "0123456789",
Currency: "CAD",
Price: 23.45,
},
expectError: false,
},
{
desc: "load error",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
},
expectedResult: nil,
expectError: true,
},
}
for _, scenario := range scenarios {
// define a mock db
testDb, dbMock, err := sqlmock.New()
require.NoError(t, err)
// configure the mock db
scenario.configureMockDB(dbMock)
// monkey db for this test
original := *db
db = testDb
// call function
result, err := Load(2)
// validate results
assert.Equal(t, scenario.expectedResult, result, scenario.desc)
assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
assert.NoError(t, dbMock.ExpectationsWereMet())
// restore original DB (after test)
db = &original
testDb.Close()
}
}
// convert SQL string to regex by treating the entire query as a literal
func convertSQLToRegex(in string) string {
return `\Q` + in + `\E`
}
================================================
FILE: ch05/acme/internal/modules/exchange/converter.go
================================================
package exchange
import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
)
const (
// request URL for the exchange rate API
urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
// default price that is sent when an error occurs
defaultPrice = 0.0
)
// Converter will convert the base price to the currency supplied
// Note: we are expecting sane inputs and therefore skipping input validation
type Converter struct{}
// Do will perform the conversion
func (c *Converter) Do(basePrice float64, currency string) (float64, error) {
// load rate from the external API
response, err := c.loadRateFromServer(currency)
if err != nil {
return defaultPrice, err
}
// extract rate from response
rate, err := c.extractRate(response, currency)
if err != nil {
return defaultPrice, err
}
// apply rate and round to 2 decimal places
return math.Floor((basePrice/rate)*100) / 100, nil
}
// load rate from the external API
func (c *Converter) loadRateFromServer(currency string) (*http.Response, error) {
// build the request
url := fmt.Sprintf(urlFormat,
config.App.ExchangeRateBaseURL,
config.App.ExchangeRateAPIKey,
currency)
// perform request
response, err := http.Get(url)
if err != nil {
logging.L.Warn("[exchange] failed to load. err: %s", err)
return nil, err
}
if response.StatusCode != http.StatusOK {
err = fmt.Errorf("request failed with code %d", response.StatusCode)
logging.L.Warn("[exchange] %s", err)
return nil, err
}
return response, nil
}
func (c *Converter) extractRate(response *http.Response, currency string) (float64, error) {
defer func() {
_ = response.Body.Close()
}()
// extract data from response
data, err := c.extractResponse(response)
if err != nil {
return defaultPrice, err
}
// pull rate from response data
rate, found := data.Quotes["USD"+currency]
if !found {
err = fmt.Errorf("response did not include expected currency '%s'", currency)
logging.L.Error("[exchange] %s", err)
return defaultPrice, err
}
// happy path
return rate, nil
}
func (c *Converter) extractResponse(response *http.Response) (*apiResponseFormat, error) {
payload, err := ioutil.ReadAll(response.Body)
if err != nil {
logging.L.Error("[exchange] failed to ready response body. err: %s", err)
return nil, err
}
data := &apiResponseFormat{}
err = json.Unmarshal(payload, data)
if err != nil {
logging.L.Error("[exchange] error converting response. err: %s", err)
return nil, err
}
// happy path
return data, nil
}
// the response format from the exchange rate API
type apiResponseFormat struct {
Quotes map[string]float64 `json:"quotes"`
}
================================================
FILE: ch05/acme/internal/modules/get/get.go
================================================
package get
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
)
var (
// error thrown when the requested person is not in the database
errPersonNotFound = errors.New("person not found")
)
// Getter will attempt to load a person.
// It can return an error caused by the data layer or when the requested person is not found
type Getter struct {
}
// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
// load person from the data layer
person, err := loader(ID)
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are hiding the implementation details from our users.
return nil, errPersonNotFound
}
return nil, err
}
return person, err
}
// this function as a variable allows us to Monkey Patch during testing
var loader = data.Load
================================================
FILE: ch05/acme/internal/modules/get/go_test.go
================================================
package get
import (
"errors"
"testing"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetter_Do_happyPath(t *testing.T) {
// inputs
ID := 1234
// monkey patch calls to the data package
defer func(original func(ID int) (*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func(ID int) (*data.Person, error) {
result := &data.Person{
ID: 1234,
FullName: "Doug",
}
var resultErr error
return result, resultErr
}
// end of monkey patch
// call method
getter := &Getter{}
person, err := getter.Do(ID)
// validate expectations
require.NoError(t, err)
assert.Equal(t, ID, person.ID)
assert.Equal(t, "Doug", person.FullName)
}
func TestGetter_Do_noSuchPerson(t *testing.T) {
// inputs
ID := 5678
// monkey patch calls to the data package
defer func(original func(ID int) (*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func(ID int) (*data.Person, error) {
var result *data.Person
resultErr := data.ErrNotFound
return result, resultErr
}
// end of monkey patch
// call method
getter := &Getter{}
person, err := getter.Do(ID)
// validate expectations
require.Equal(t, errPersonNotFound, err)
assert.Nil(t, person)
}
func TestGetter_Do_error(t *testing.T) {
// inputs
ID := 1234
// monkey patch calls to the data package
defer func(original func(ID int) (*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func(ID int) (*data.Person, error) {
var result *data.Person
resultErr := errors.New("failed to load person")
return result, resultErr
}
// end of monkey patch
// call method
getter := &Getter{}
person, err := getter.Do(ID)
// validate expectations
require.Error(t, err)
assert.Nil(t, person)
}
================================================
FILE: ch05/acme/internal/modules/list/list.go
================================================
package list
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
)
var (
// error thrown when there are no people in the database
errPeopleNotFound = errors.New("no people found")
)
// Lister will attempt to load all people in the database.
// It can return an error caused by the data layer
type Lister struct {
}
// Do will load the people from the data layer
func (l *Lister) Do() ([]*data.Person, error) {
// load all people
people, err := l.load()
if err != nil {
return nil, err
}
if len(people) == 0 {
// special processing for 0 people returned
return nil, errPeopleNotFound
}
return people, nil
}
// load all people
func (l *Lister) load() ([]*data.Person, error) {
people, err := loader()
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are encapsulating the implementation details from our users.
return nil, errPeopleNotFound
}
return nil, err
}
return people, nil
}
// this function as a variable allows us to Monkey Patch during testing
var loader = data.LoadAll
================================================
FILE: ch05/acme/internal/modules/list/list_test.go
================================================
package list
import (
"errors"
"testing"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLister_Do_happyPath(t *testing.T) {
// monkey patch calls to the data package
defer func(original func() ([]*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func() ([]*data.Person, error) {
result := []*data.Person{
{
ID: 1234,
FullName: "Sally",
},
{
ID: 5678,
FullName: "Jane",
},
}
var resultErr error
return result, resultErr
}
// end of monkey patch
// call method
lister := &Lister{}
persons, err := lister.load()
// validate expectations
require.NoError(t, err)
assert.Equal(t, 2, len(persons))
}
func TestLister_Do_noResults(t *testing.T) {
// monkey patch calls to the data package
defer func(original func() ([]*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func() ([]*data.Person, error) {
var result []*data.Person
resultErr := data.ErrNotFound
return result, resultErr
}
// end of monkey patch
// call method
lister := &Lister{}
persons, err := lister.load()
// validate expectations
require.Equal(t, errPeopleNotFound, err)
assert.Equal(t, 0, len(persons))
}
func TestLister_Do_error(t *testing.T) {
// monkey patch calls to the data package
defer func(original func() ([]*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func() ([]*data.Person, error) {
var result []*data.Person
resultErr := errors.New("failed to load people")
return result, resultErr
}
// end of monkey patch
// call method
lister := &Lister{}
persons, err := lister.load()
// validate expectations
require.Error(t, err)
assert.Equal(t, 0, len(persons))
}
================================================
FILE: ch05/acme/internal/modules/register/register.go
================================================
package register
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/exchange"
)
const (
// default person id (returned on error)
defaultPersonID = 0
)
var (
// validation errors
errNameMissing = errors.New("name is missing")
errPhoneMissing = errors.New("phone is missing")
errCurrencyMissing = errors.New("currency is missing")
errInvalidCurrency = errors.New("currency is invalid, supported types are AUD, CNY, EUR, GBP, JPY, MYR, SGD, USD")
// a little trick to make checking for supported currencies easier
supportedCurrencies = map[string]struct{}{
"AUD": {},
"CNY": {},
"EUR": {},
"GBP": {},
"JPY": {},
"MYR": {},
"SGD": {},
"USD": {},
}
)
// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}
// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
// validate the request
err := r.validateInput(in)
if err != nil {
logging.L.Warn("input validation failed with err: %s", err)
return defaultPersonID, err
}
// get price in the requested currency
price, err := r.getPrice(in.Currency)
if err != nil {
return defaultPersonID, err
}
// save registration
id, err := r.save(in, price)
if err != nil {
// no need to log here as we expect the data layer to do so
return defaultPersonID, err
}
return id, nil
}
// validate input and return error on fail
func (r *Registerer) validateInput(in *data.Person) error {
if in.FullName == "" {
return errNameMissing
}
if in.Phone == "" {
return errPhoneMissing
}
if in.Currency == "" {
return errCurrencyMissing
}
if _, found := supportedCurrencies[in.Currency]; !found {
return errInvalidCurrency
}
// happy path
return nil
}
// get price in the requested currency
func (r *Registerer) getPrice(currency string) (float64, error) {
converter := &exchange.Converter{}
price, err := converter.Do(config.App.BasePrice, currency)
if err != nil {
logging.L.Warn("failed to convert the price. err: %s", err)
return defaultPersonID, err
}
return price, nil
}
// save the registration
func (r *Registerer) save(in *data.Person, price float64) (int, error) {
person := &data.Person{
FullName: in.FullName,
Phone: in.Phone,
Currency: in.Currency,
Price: price,
}
return saver(person)
}
// this function as a variable allows us to Monkey Patch during testing
var saver = data.Save
================================================
FILE: ch05/acme/internal/modules/register/register_test.go
================================================
package register
import (
"errors"
"testing"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRegisterer_Do_happyPath(t *testing.T) {
// monkey patch calls to the data package
defer func(original func(in *data.Person) (int, error)) {
// restore original
saver = original
}(saver)
// replace method
saver = func(in *data.Person) (int, error) {
result := 888
var resultErr error
return result, resultErr
}
// end of monkey patch
// inputs
in := &data.Person{
FullName: "Chang",
Phone: "11122233355",
Currency: "CNY",
}
// call method
registerer := &Registerer{}
ID, err := registerer.Do(in)
// validate expectations
require.NoError(t, err)
assert.Equal(t, 888, ID)
}
func TestRegisterer_Do_error(t *testing.T) {
// monkey patch calls to the data package
defer func(original func(in *data.Person) (int, error)) {
// restore original
saver = original
}(saver)
// replace method
saver = func(in *data.Person) (int, error) {
var result int
resultErr := errors.New("failed to save")
return result, resultErr
}
// end of monkey patch
// inputs
in := &data.Person{
FullName: "Chang",
Phone: "11122233355",
Currency: "CNY",
}
// call method
registerer := &Registerer{}
ID, err := registerer.Do(in)
// validate expectations
require.Error(t, err)
assert.Equal(t, 0, ID)
}
================================================
FILE: ch05/acme/internal/rest/common_test.go
================================================
package rest
import (
"context"
"net"
)
func getOpenPort() (string, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return "", err
}
address := listener.Addr().String()
listener.Close()
return address, nil
}
func startServer(ctx context.Context) (string, error) {
// get open port
address, err := getOpenPort()
if err != nil {
return "", err
}
// start a server
server := New(address)
go server.Listen(ctx.Done())
// wait for server to be ready
dialer := &net.Dialer{}
for {
conn, _ := dialer.DialContext(ctx, "tcp", address)
if conn != nil {
defer conn.Close()
return address, nil
}
select {
case <-ctx.Done():
return "", ctx.Err()
default:
// try again
}
}
return address, nil
}
================================================
FILE: ch05/acme/internal/rest/get.go
================================================
package rest
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/logging"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/get"
"github.com/gorilla/mux"
)
const (
// default person id (returned on error)
defaultPersonID = 0
)
// GetHandler is the HTTP handler for the "Get Person" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400
// or "not found" HTTP 404
// There are some programmer errors possible but hopefully these will be caught in testing.
type GetHandler struct {
}
// ServeHTTP implements http.Handler
func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract person id from request
id, err := h.extractID(request)
if err != nil {
// output error
response.WriteHeader(http.StatusBadRequest)
return
}
// attempt get
getter := get.Getter{}
person, err := getter.Do(id)
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusNotFound)
return
}
// happy path
err = h.writeJSON(response, person)
if err != nil {
// this error should not happen but if it does there is nothing we can do to recover
response.WriteHeader(http.StatusInternalServerError)
}
}
// extract the person ID from the request
func (h *GetHandler) extractID(request *http.Request) (int, error) {
// ID is part of the URL, so we extract it from there
vars := mux.Vars(request)
idAsString, exists := vars["id"]
if !exists {
// log and return error
err := errors.New("[get] person id missing from request")
logging.L.Warn(err.Error())
return defaultPersonID, err
}
// convert ID to int
id, err := strconv.Atoi(idAsString)
if err != nil {
// log and return error
err = fmt.Errorf("[get] failed to convert person id into a number. err: %s", err)
logging.L.Error(err.Error())
return defaultPersonID, err
}
return id, nil
}
// output the supplied person as JSON
func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) error {
output := &getResponseFormat{
ID: person.ID,
FullName: person.FullName,
Phone: person.Phone,
Currency: person.Currency,
Price: person.Price,
}
// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
return json.NewEncoder(writer).Encode(output)
}
// the JSON response format
type getResponseFormat struct {
ID int `json:"id"`
FullName string `json:"name"`
Phone string `json:"phone"`
Currency string `json:"currency"`
Price float64 `json:"price"`
}
================================================
FILE: ch05/acme/internal/rest/get_test.go
================================================
package rest
import (
"context"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
response, err := http.Get("http://" + address + "/person/1/")
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
expectedPayload := []byte(`{"id":1,"name":"John","phone":"0123456780","currency":"USD","price":100}` + "\n")
payload, _ := ioutil.ReadAll(response.Body)
defer response.Body.Close()
assert.Equal(t, expectedPayload, payload)
}
================================================
FILE: ch05/acme/internal/rest/list.go
================================================
package rest
import (
"encoding/json"
"io"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/list"
)
// ListHandler is the HTTP handler for the "List Do people" endpoint
// In this simplified example we are assuming all possible errors are system errors (HTTP 500)
type ListHandler struct {
}
// ServeHTTP implements http.Handler
func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// attempt loadAll
lister := list.Lister{}
people, err := lister.Do()
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusNotFound)
return
}
// happy path
err = h.writeJSON(response, people)
if err != nil {
// this error should not happen but if it does there is nothing we can do to recover
response.WriteHeader(http.StatusInternalServerError)
}
}
// output the result as JSON
func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Person) error {
output := &listResponseFormat{
People: make([]*listResponseItemFormat, len(people)),
}
for index, record := range people {
output.People[index] = &listResponseItemFormat{
ID: record.ID,
FullName: record.FullName,
Phone: record.Phone,
}
}
// call to http.ResponseWriter.Write() will cause HTTP OK (200) to be output as well
return json.NewEncoder(writer).Encode(output)
}
type listResponseFormat struct {
People []*listResponseItemFormat `json:"people"`
}
type listResponseItemFormat struct {
ID int `json:"id"`
FullName string `json:"name"`
Phone string `json:"phone"`
}
================================================
FILE: ch05/acme/internal/rest/list_test.go
================================================
package rest
import (
"context"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestListHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
response, err := http.Get("http://" + address + "/person/list")
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
expectedPayload := []byte(`{"people":[{"id":1,"name":"John","phone":"0123456780"},{"id":2,"name":"Paul","phone":"0123456781"},{"id":3,"name":"George","phone":"0123456782"},{"id":4,"name":"Ringo","phone":"0123456783"}`)
payload, _ := ioutil.ReadAll(response.Body)
defer response.Body.Close()
// we have to use contains because other tests add more records
assert.Contains(t, string(payload), string(expectedPayload))
}
================================================
FILE: ch05/acme/internal/rest/not_found.go
================================================
package rest
import (
"net/http"
)
func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
response.WriteHeader(http.StatusNotFound)
_, _ = response.Write([]byte(`Not found`))
}
================================================
FILE: ch05/acme/internal/rest/not_found_test.go
================================================
package rest
import (
"context"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestNotFoundHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
response, err := http.Get("http://" + address + "/some-bad-address")
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusNotFound, response.StatusCode)
}
================================================
FILE: ch05/acme/internal/rest/register.go
================================================
package rest
import (
"encoding/json"
"fmt"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/modules/register"
)
// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
}
// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract payload from request
requestPayload, err := h.extractPayload(request)
if err != nil {
// output error
response.WriteHeader(http.StatusBadRequest)
return
}
// register person
id, err := h.register(requestPayload)
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusBadRequest)
return
}
// happy path
response.Header().Add("Location", fmt.Sprintf("/person/%d/", id))
response.WriteHeader(http.StatusCreated)
}
// extract payload from request
func (h *RegisterHandler) extractPayload(request *http.Request) (*registerRequest, error) {
requestPayload := ®isterRequest{}
decoder := json.NewDecoder(request.Body)
err := decoder.Decode(requestPayload)
if err != nil {
return nil, err
}
return requestPayload, nil
}
// call the logic layer
func (h *RegisterHandler) register(requestPayload *registerRequest) (int, error) {
person := &data.Person{
FullName: requestPayload.FullName,
Phone: requestPayload.Phone,
Currency: requestPayload.Currency,
}
registerer := ®ister.Registerer{}
return registerer.Do(person)
}
// register endpoint request format
type registerRequest struct {
// FullName of the person
FullName string `json:"fullName"`
// Phone of the person
Phone string `json:"phone"`
// Currency the wish to register in
Currency string `json:"currency"`
}
================================================
FILE: ch05/acme/internal/rest/register_test.go
================================================
package rest
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRegisterHandler_ServeHTTP(t *testing.T) {
// ensure the test always fails by giving it a timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create and start a server
// With out current implementation, we cannot test this handler without a full server as we need the mux.
address, err := startServer(ctx)
require.NoError(t, err)
// build inputs
validRequest := buildValidRequest()
response, err := http.Post("http://"+address+"/person/register", "application/json", validRequest)
// validate outputs
require.NoError(t, err)
require.Equal(t, http.StatusCreated, response.StatusCode)
defer response.Body.Close()
// call should output the location to the new person
headerLocation := response.Header.Get("Location")
assert.Contains(t, headerLocation, "/person/")
}
func buildValidRequest() io.Reader {
requestData := ®isterRequest{
FullName: "Joan Smith",
Currency: "AUD",
Phone: "01234567890",
}
data, _ := json.Marshal(requestData)
return bytes.NewBuffer(data)
}
================================================
FILE: ch05/acme/internal/rest/server.go
================================================
package rest
import (
"net/http"
"github.com/gorilla/mux"
)
// New will create and initialize the server
func New(address string) *Server {
return &Server{
address: address,
handlerGet: &GetHandler{},
handlerList: &ListHandler{},
handlerNotFound: notFoundHandler,
handlerRegister: &RegisterHandler{},
}
}
// Server is the HTTP REST server
type Server struct {
address string
server *http.Server
handlerGet http.Handler
handlerList http.Handler
handlerNotFound http.HandlerFunc
handlerRegister http.Handler
}
// Listen will start a HTTP rest for this service
func (s *Server) Listen(stop <-chan struct{}) {
router := s.buildRouter()
// create the HTTP server
s.server = &http.Server{
Handler: router,
Addr: s.address,
}
// listen for shutdown
go func() {
// wait for shutdown signal
<-stop
_ = s.server.Close()
}()
// start the HTTP server
_ = s.server.ListenAndServe()
}
// configure the endpoints to handlers
func (s *Server) buildRouter() http.Handler {
router := mux.NewRouter()
// map URL endpoints to HTTP handlers
router.Handle("/person/{id}/", s.handlerGet).Methods("GET")
router.Handle("/person/list", s.handlerList).Methods("GET")
router.Handle("/person/register", s.handlerRegister).Methods("POST")
// convert a "catch all" not found handler
router.NotFoundHandler = s.handlerNotFound
return router
}
================================================
FILE: ch05/acme/main.go
================================================
package main
import (
"context"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/rest"
)
func main() {
// bind stop channel to context
ctx := context.Background()
// start REST server
server := rest.New(config.App.Address)
server.Listen(ctx.Done())
}
================================================
FILE: ch05/fake.go
================================================
package ch05
func init() {
// This file is included so that Go tools (like `go list`) will find Go code in this directory and not error
}
================================================
FILE: ch06/01_constructor_injection/01_welcome_email.go
================================================
package constructor_injection
import (
"errors"
)
func NewWelcomeSender(in *Mailer) (*WelcomeSender, error) {
// guard clause
if in == nil {
return nil, errors.New("programmer error: mailer must not provided")
}
return &WelcomeSender{
mailer: in,
}, nil
}
func NewWelcomeSenderNoGuard(in *Mailer) *WelcomeSender {
return &WelcomeSender{
mailer: in,
}
}
// WelcomeSender sends a Welcome email to new users
type WelcomeSender struct {
mailer *Mailer
}
func (w *WelcomeSender) Send(to string) error {
body := w.buildMessage()
return w.mailer.Send(to, body)
}
// build and return the message body
func (w *WelcomeSender) buildMessage() string {
return ""
}
// Mailer sends and receives emails
type Mailer struct {
Host string
Port string
Username string
Password string
}
func (m *Mailer) Send(to string, body string) error {
// send email
return nil
}
func (m *Mailer) Receive(address string) (string, error) {
// receive email
return "", nil
}
================================================
FILE: ch06/01_constructor_injection/01_welcome_email_test.go
================================================
package constructor_injection
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewWelcomeSender_happyPath(t *testing.T) {
sender, err := NewWelcomeSender(&Mailer{})
assert.NotNil(t, sender)
assert.NoError(t, err)
}
func TestNewWelcomeSender_guardClause(t *testing.T) {
sender, err := NewWelcomeSender(nil)
assert.Nil(t, sender)
assert.Error(t, err)
}
func TestNewWelcomeSenderNoGuard_happyPath(t *testing.T) {
sender := NewWelcomeSenderNoGuard(&Mailer{})
assert.NotNil(t, sender)
}
================================================
FILE: ch06/01_constructor_injection/02_mailer_interface.go
================================================
package constructor_injection
// Mailer sends and receives emails
type MailerInterface interface {
Send(to string, body string) error
Receive(address string) (string, error)
}
================================================
FILE: ch06/01_constructor_injection/03_sender_interface.go
================================================
package constructor_injection
type Sender interface {
Send(to string, body string) error
}
func NewWelcomeSenderV2(in Sender) *WelcomeSenderV2 {
return &WelcomeSenderV2{
sender: in,
}
}
// WelcomeSenderV2 sends a Welcome email to new users
type WelcomeSenderV2 struct {
sender Sender
}
func (w *WelcomeSenderV2) Send(to string) error {
body := w.buildMessage()
return w.sender.Send(to, body)
}
// build and return the message body
func (w *WelcomeSenderV2) buildMessage() string {
return ""
}
================================================
FILE: ch06/01_constructor_injection/05_duck_typing.go
================================================
package constructor_injection
import (
"fmt"
)
type Talker interface {
Speak() string
Shout() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
func (d Dog) Shout() string {
return "WOOF!"
}
func SpeakExample() {
var talker Talker
talker = Dog{}
fmt.Print(talker.Speak())
}
================================================
FILE: ch06/02_advantages/01_easy_to_implement.go
================================================
package advantages
// WelcomeSender sends a Welcome email to new users
type WelcomeSender struct {
Mailer *Mailer
}
func (w *WelcomeSender) Send(to string) error {
body := w.buildMessage()
return w.Mailer.Send(to, body)
}
// build and return the message body
func (w *WelcomeSender) buildMessage() string {
return ""
}
// Mailer will send an email
type Mailer struct{}
func (m *Mailer) Send(to string, body string) error {
// send email
return nil
}
================================================
FILE: ch06/02_advantages/01_easy_to_implement_example_test.go
================================================
package advantages_test
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages"
)
func ExampleWelcomeSender_Send() {
welcomeSender := &advantages.WelcomeSender{
Mailer: &advantages.Mailer{},
}
welcomeSender.Send("me@home.com")
}
================================================
FILE: ch06/02_advantages/02_easy_to_implement.go
================================================
package advantages
func NewWelcomeSenderV2(mailer *Mailer) *WelcomeSenderV2 {
return &WelcomeSenderV2{
mailer: mailer,
}
}
// WelcomeSenderV2 sends a Welcome email to new users
type WelcomeSenderV2 struct {
mailer *Mailer
}
func (w *WelcomeSenderV2) Send(to string) error {
body := w.buildMessage()
return w.mailer.Send(to, body)
}
// build and return the message body
func (w *WelcomeSenderV2) buildMessage() string {
return ""
}
================================================
FILE: ch06/02_advantages/02_easy_to_implement_example_test.go
================================================
package advantages_test
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages"
)
func ExampleWelcomeSenderV2_Send() {
welcomeSender := advantages.NewWelcomeSenderV2(&advantages.Mailer{})
welcomeSender.Send("me@home.com")
}
================================================
FILE: ch06/02_advantages/03_predictable.go
================================================
package advantages
import (
"errors"
)
type Engine interface {
Start()
IncreasePower()
DecreasePower()
Stop()
IsRunning() bool
}
type Car struct {
Engine Engine
}
func (c *Car) Drive() error {
if c.Engine == nil {
return errors.New("engine ie missing")
}
// use the engine
c.Engine.Start()
c.Engine.IncreasePower()
return nil
}
func (c *Car) Stop() error {
if c.Engine == nil {
return errors.New("engine ie missing")
}
// use the engine
c.Engine.DecreasePower()
c.Engine.Stop()
return nil
}
================================================
FILE: ch06/02_advantages/04_predictable.go
================================================
package advantages
import (
"errors"
)
func NewCarV2(engine Engine) (*CarV2, error) {
if engine == nil {
return nil, errors.New("invalid engine supplied")
}
return &CarV2{
engine: engine,
}, nil
}
type CarV2 struct {
engine Engine
}
func (c *CarV2) Drive() error {
// use the engine
c.engine.Start()
c.engine.IncreasePower()
return nil
}
func (c *CarV2) Stop() error {
// use the engine
c.engine.DecreasePower()
c.engine.Stop()
return nil
}
================================================
FILE: ch06/02_advantages/05_encapsulation.go
================================================
package advantages
import (
"errors"
)
func (c *CarV2) FillPetrolTank() error {
// use the engine
if c.engine.IsRunning() {
return errors.New("cannot fill the tank while the engine is running")
}
// fill the tank!
return c.fill()
}
func (c CarV2) fill() error {
// TODO: implement
return nil
}
================================================
FILE: ch06/02_advantages/06_encapsulation.go
================================================
package advantages
import (
"errors"
)
func (c *CarV2) FillPetrolTankV2(engine Engine) error {
// use the engine
if engine.IsRunning() {
return errors.New("cannot fill the tank while the engine is running")
}
// fill the tank!
return c.fill()
}
================================================
FILE: ch06/03_applying/01/01_register_handler_before.go
================================================
package rest
import (
"encoding/json"
"fmt"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/register"
)
// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
}
// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// extract payload from request
requestPayload, err := h.extractPayload(request)
if err != nil {
// output error
response.WriteHeader(http.StatusBadRequest)
return
}
// register person
id, err := h.register(requestPayload)
if err != nil {
// not need to log here as we can expect other layers to do so
response.WriteHeader(http.StatusBadRequest)
return
}
// happy path
response.Header().Add("Location", fmt.Sprintf("/person/%d/", id))
response.WriteHeader(http.StatusCreated)
}
// extract payload from request
func (h *RegisterHandler) extractPayload(request *http.Request) (*registerRequest, error) {
requestPayload := ®isterRequest{}
decoder := json.NewDecoder(request.Body)
err := decoder.Decode(requestPayload)
if err != nil {
return nil, err
}
return requestPayload, nil
}
// call the logic layer
func (h *RegisterHandler) register(requestPayload *registerRequest) (int, error) {
person := &data.Person{
FullName: requestPayload.FullName,
Phone: requestPayload.Phone,
Currency: requestPayload.Currency,
}
registerer := ®ister.Registerer{}
return registerer.Do(person)
}
// register endpoint request format
type registerRequest struct {
// FullName of the person
FullName string `json:"fullName"`
// Phone of the person
Phone string `json:"phone"`
// Currency the wish to register in
Currency string `json:"currency"`
}
================================================
FILE: ch06/03_applying/01/data/person.go
================================================
package data
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
================================================
FILE: ch06/03_applying/01/register/register.go
================================================
package register
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
)
// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}
// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
// fake implementation
return 0, nil
}
================================================
FILE: ch06/03_applying/02/01_register_handler.go
================================================
package rest
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/02/register"
)
// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
registerer *register.Registerer
}
================================================
FILE: ch06/03_applying/02/data/person.go
================================================
package data
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
================================================
FILE: ch06/03_applying/02/register/register.go
================================================
package register
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data"
)
// Registerer validates the supplied person, calculates the price in the requested currency and saves the result.
// It will return an error when:
// -the person object does not include all the fields
// -the currency is invalid
// -the exchange rate cannot be loaded
// -the data layer throws an error.
type Registerer struct {
}
// Do is API for this struct
func (r *Registerer) Do(in *data.Person) (int, error) {
// fake implementation
return 0, nil
}
================================================
FILE: ch06/03_applying/03/data/person.go
================================================
package data
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
================================================
FILE: ch06/03_applying/03/mock_register_model_test.go
================================================
// Code generated by mockery v1.0.0
// @generated
package rest
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/03/data"
"github.com/stretchr/testify/mock"
)
// MockRegisterModel is an autogenerated mock type for the RegisterModel type
type MockRegisterModel struct {
mock.Mock
}
// Do provides a mock function with given fields: in
func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {
ret := _m.Called(in)
var r0 int
if rf, ok := ret.Get(0).(func(*data.Person) int); ok {
r0 = rf(in)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(*data.Person) error); ok {
r1 = rf(in)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
================================================
FILE: ch06/03_applying/03/register_test.go
================================================
package rest
import (
"net/http"
"testing"
)
func TestRegisterHandler_ServeHTTP(t *testing.T) {
scenarios := []struct {
desc string
inRequest func() *http.Request
inModelMock func() *MockRegisterModel
expectedStatus int
expectedHeader string
}{
// scenarios go here
}
for _, s := range scenarios {
scenario := s
t.Run(scenario.desc, func(t *testing.T) {
// test goes here
})
}
}
================================================
FILE: ch06/03_applying/04/data/person.go
================================================
package data
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
================================================
FILE: ch06/03_applying/04/mock_register_model_test.go
================================================
// Code generated by mockery v1.0.0
// @generated
package rest
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/04/data"
"github.com/stretchr/testify/mock"
)
// MockRegisterModel is an autogenerated mock type for the RegisterModel type
type MockRegisterModel struct {
mock.Mock
}
// Do provides a mock function with given fields: in
func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {
ret := _m.Called(in)
var r0 int
if rf, ok := ret.Get(0).(func(*data.Person) int); ok {
r0 = rf(in)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(*data.Person) error); ok {
r1 = rf(in)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
================================================
FILE: ch06/03_applying/04/register.go
================================================
package rest
import (
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/04/data"
)
// RegisterModel will validate and save a registration
type RegisterModel interface {
Do(in *data.Person) (int, error)
}
// RegisterHandler is the HTTP handler for the "Register" endpoint
// In this simplified example we are assuming all possible errors are user errors and returning "bad request" HTTP 400.
// There are some programmer errors possible but hopefully these will be caught in testing.
type RegisterHandler struct {
registerer RegisterModel
}
// ServeHTTP implements http.Handler
func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// implementation goes here
}
// register endpoint request format
type registerRequest struct {
// FullName of the person
FullName string `json:"fullName"`
// Phone of the person
Phone string `json:"phone"`
// Currency the wish to register in
Currency string `json:"currency"`
}
================================================
FILE: ch06/03_applying/04/register_test.go
================================================
package rest
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRegisterHandler_ServeHTTP(t *testing.T) {
scenarios := []struct {
desc string
inRequest func() *http.Request
inModelMock func() *MockRegisterModel
expectedStatus int
expectedHeader string
}{
// scenarios go here
}
for _, s := range scenarios {
scenario := s
t.Run(scenario.desc, func(t *testing.T) {
// define model layer mock
mockRegisterModel := scenario.inModelMock()
// build handler
handler := &RegisterHandler{
registerer: mockRegisterModel,
}
// perform request
response := httptest.NewRecorder()
handler.ServeHTTP(response, scenario.inRequest())
// validate outputs
require.Equal(t, scenario.expectedStatus, response.Code)
// call should output the location to the new person
resultHeader := response.Header().Get("Location")
assert.Equal(t, scenario.expectedHeader, resultHeader)
// validate the mock was used as we expected
assert.True(t, mockRegisterModel.AssertExpectations(t))
})
}
}
func buildValidRequest() io.Reader {
requestData := ®isterRequest{
FullName: "Joan Smith",
Currency: "AUD",
Phone: "01234567890",
}
data, _ := json.Marshal(requestData)
return bytes.NewBuffer(data)
}
================================================
FILE: ch06/03_applying/05/data/person.go
================================================
package data
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
================================================
FILE: ch06/03_applying/05/fakes.go
================================================
package reset
import (
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)
func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
response.WriteHeader(http.StatusNotFound)
_, _ = response.Write([]byte(`Not found`))
}
// Fake/Stub implementations to make the compiler happy
type Server struct {
address string
handlerGet http.Handler
handlerList http.Handler
handlerNotFound http.HandlerFunc
handlerRegister http.Handler
}
func NewGetHandler(_ GetModel) *GetHandler {
return &GetHandler{}
}
type GetModel interface {
Do(ID int) (*data.Person, error)
}
type GetHandler struct{}
func (g *GetHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {}
func NewListHandler(_ ListModel) *ListHandler {
return &ListHandler{}
}
type ListModel interface {
Do() ([]*data.Person, error)
}
type ListHandler struct {
}
func (l *ListHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {}
func NewRegisterHandler(_ RegisterModel) *RegisterHandler {
return &RegisterHandler{}
}
type RegisterModel interface {
Do(in *data.Person) (int, error)
}
type RegisterHandler struct {
}
func (r *RegisterHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {}
================================================
FILE: ch06/03_applying/05/get/getter.go
================================================
package get
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)
// Stub implementation so that the example compiles
type Getter struct{}
func (g *Getter) Do(ID int) (*data.Person, error) {
return nil, nil
}
================================================
FILE: ch06/03_applying/05/list/lister.go
================================================
package list
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)
// Stub implementation so that the example compiles
type Lister struct{}
func (l *Lister) Do() ([]*data.Person, error) {
return nil, nil
}
================================================
FILE: ch06/03_applying/05/register/registerer.go
================================================
package register
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data"
)
// Stub implementation so that the example compiles
type Registerer struct{}
func (r *Registerer) Do(in *data.Person) (int, error) {
return 0, nil
}
================================================
FILE: ch06/03_applying/05/server.go
================================================
package reset
import (
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/get"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/list"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/register"
)
// New will create and initialize the server
func New(address string) *Server {
return &Server{
address: address,
handlerGet: NewGetHandler(&get.Getter{}),
handlerList: NewListHandler(&list.Lister{}),
handlerNotFound: notFoundHandler,
handlerRegister: NewRegisterHandler(®ister.Registerer{}),
}
}
================================================
FILE: ch06/04_disadvantages/01_lots_of_changes.go
================================================
package disadvantages
// Dealer will shuffle a deck of cards and deal them to the players
func DealCards() (player1 []Card, player2 []Card) {
// create a new deck of cards
cards := newDeck()
// shuffle the cards
shuffler := &myShuffler{}
shuffler.Shuffle(cards)
// deal
player1 = append(player1, cards[0])
player2 = append(player2, cards[1])
player1 = append(player1, cards[2])
player2 = append(player2, cards[3])
return
}
// returns a new deck of cards
func newDeck() []Card {
return []Card{
// code removed
}
}
// Shuffler will shuffle (randomize) the supplied cards
type Shuffler interface {
Shuffle(cards []Card)
}
// Card is single Playing Card
type Card struct {
Suit string
Value string
}
// implements Shuffler
type myShuffler struct{}
// Shuffle implements shuffler
func (m *myShuffler) Shuffle(cards []Card) {
// randomize the cards
}
================================================
FILE: ch06/04_disadvantages/02_overuse.go
================================================
package disadvantages
import (
"encoding/json"
"io/ioutil"
"net/http"
)
const downstreamServer = "http://www.example.com"
// FetchRates rates from downstream service
type FetchRates struct{}
func (f *FetchRates) Fetch() ([]Rate, error) {
// build the URL from which to fetch the rates
url := downstreamServer + "/rates"
// build request
request, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
// fetch rates
response, err := http.DefaultClient.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
// read the content of the response
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
// convert JSON bytes to Go structs
out := &downstreamResponse{}
err = json.Unmarshal(data, out)
if err != nil {
return nil, err
}
return out.Rates, nil
}
// response format from the downstream service
type downstreamResponse struct {
Rates []Rate `json:"rates"`
}
type Rate struct {
Code string
Value float64
}
================================================
FILE: ch06/04_disadvantages/03_non_obvious.go
================================================
package disadvantages
import (
"errors"
)
// NewClient creates and initialises the client
func NewClient(service DepService) Client {
return &clientImpl{
service: service,
}
}
// Client is the exported API
type Client interface {
DoSomethingUseful() (bool, error)
}
// implement Client
type clientImpl struct {
service DepService
}
func (c *clientImpl) DoSomethingUseful() (bool, error) {
// this function does something useful
return false, errors.New("not implemented")
}
type DepService interface {
DoSomethingElse()
}
================================================
FILE: ch06/04_disadvantages/04_non_obvious_example_test.go
================================================
package disadvantages_test
func Example() {
}
// StubClient is a stub implementation of disadvantages.Client interface
type StubClient struct{}
// DoSomethingUseful implements disadvantages.Client
func (s *StubClient) DoSomethingUseful() (bool, error) {
return true, nil
}
================================================
FILE: ch06/04_disadvantages/05_constructors.go
================================================
package disadvantages
type InnerService struct {
innerDep Dependency
}
func NewInnerService(innerDep Dependency) *InnerService {
return &InnerService{
innerDep: innerDep,
}
}
type OuterService struct {
// composition
innerService *InnerService
outerDep Dependency
}
func NewOuterService(outerDep Dependency, innerDep Dependency) *OuterService {
return &OuterService{
innerService: NewInnerService(innerDep),
outerDep: outerDep,
}
}
// fake type to satisfy the compiler
type Dependency interface {
}
================================================
FILE: ch06/acme/internal/config/config.go
================================================
package config
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/logging"
)
// DefaultEnvVar is the default environment variable the points to the config file
const DefaultEnvVar = "ACME_CONFIG"
// App is the application config
var App *Config
// Config defines the JSON format for the config file
type Config struct {
// DSN is the data source name (format: https://github.com/go-sql-driver/mysql/#dsn-data-source-name)
DSN string
// Address is the IP address and port to bind this rest to
Address string
// BasePrice is the price of registration
BasePrice float64
// ExchangeRateBaseURL is the server and protocol part of the URL from which to load the exchange rate
ExchangeRateBaseURL string
// ExchangeRateAPIKey is the API for the exchange rate API
ExchangeRateAPIKey string
}
// Load returns the config loaded from environment
func init() {
filename, found := os.LookupEnv(DefaultEnvVar)
if !found {
logging.L.Error("failed to locate file specified by %s", DefaultEnvVar)
return
}
_ = load(filename)
}
func load(filename string) error {
App = &Config{}
bytes, err := ioutil.ReadFile(filename)
if err != nil {
logging.L.Error("failed to read config file. err: %s", err)
return err
}
err = json.Unmarshal(bytes, App)
if err != nil {
logging.L.Error("failed to parse config file. err : %s", err)
return err
}
return nil
}
================================================
FILE: ch06/acme/internal/config/config_test.go
================================================
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoad(t *testing.T) {
scenarios := []struct {
desc string
in string
expectedConfig *Config
expectError bool
}{
{
desc: "happy path",
in: "../../../../default-config.json",
expectedConfig: &Config{
DSN: "[insert your db config here]",
Address: "0.0.0.0:8080",
BasePrice: 100.00,
ExchangeRateBaseURL: "http://apilayer.net",
ExchangeRateAPIKey: "[insert your API key here]",
},
expectError: false,
},
{
desc: "invalid path",
in: "invalid.json",
expectedConfig: &Config{},
expectError: true,
},
}
for _, s := range scenarios {
scenario := s
t.Run(scenario.desc, func(t *testing.T) {
resultErr := load(scenario.in)
require.Equal(t, scenario.expectError, resultErr != nil, "err: %s", resultErr)
assert.Equal(t, scenario.expectedConfig, App, scenario.desc)
})
}
}
================================================
FILE: ch06/acme/internal/logging/logging.go
================================================
package logging
import (
"fmt"
)
// L is the global instance of the logger
var L = &LoggerStdOut{}
// LoggerStdOut logs to std out
type LoggerStdOut struct{}
// Debug logs messages at DEBUG level
func (l LoggerStdOut) Debug(message string, args ...interface{}) {
fmt.Printf("[DEBUG] "+message, args...)
}
// Info logs messages at INFO level
func (l LoggerStdOut) Info(message string, args ...interface{}) {
fmt.Printf("[INFO] "+message, args...)
}
// Warn logs messages at WARN level
func (l LoggerStdOut) Warn(message string, args ...interface{}) {
fmt.Printf("[WARN] "+message, args...)
}
// Error logs messages at ERROR level
func (l LoggerStdOut) Error(message string, args ...interface{}) {
fmt.Printf("[ERROR] "+message, args...)
}
================================================
FILE: ch06/acme/internal/modules/data/data.go
================================================
package data
import (
"database/sql"
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/logging"
// import the MySQL Driver
_ "github.com/go-sql-driver/mysql"
)
const (
// default person id (returned on error)
defaultPersonID = 0
// SQL statements as constants (to reduce duplication and maintenance in tests)
sqlAllColumns = "id, fullname, phone, currency, price"
sqlInsert = "INSERT INTO person (fullname, phone, currency, price) VALUES (?, ?, ?, ?)"
sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? LIMIT 1"
)
var (
db *sql.DB
// ErrNotFound is returned when the no records where matched by the query
ErrNotFound = errors.New("not found")
)
var getDB = func() (*sql.DB, error) {
if db == nil {
if config.App == nil {
return nil, errors.New("config is not initialized")
}
var err error
db, err = sql.Open("mysql", config.App.DSN)
if err != nil {
// if the DB cannot be accessed we are dead
panic(err.Error())
}
}
return db, nil
}
// Person is the data transfer object (DTO) for this package
type Person struct {
// ID is the unique ID for this person
ID int
// FullName is the name of this person
FullName string
// Phone is the phone for this person
Phone string
// Currency is the currency this person has paid in
Currency string
// Price is the amount (in the above currency) paid by this person
Price float64
}
// Save will save the supplied person and return the ID of the newly created person or an error.
// Errors returned are caused by the underlying database or our connection to it.
func Save(in *Person) (int, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return defaultPersonID, err
}
// perform DB insert
result, err := db.Exec(sqlInsert, in.FullName, in.Phone, in.Currency, in.Price)
if err != nil {
logging.L.Error("failed to save person into DB. err: %s", err)
return defaultPersonID, err
}
// retrieve and return the ID of the person created
id, err := result.LastInsertId()
if err != nil {
logging.L.Error("failed to retrieve id of last saved person. err: %s", err)
return defaultPersonID, err
}
return int(id), nil
}
// LoadAll will attempt to load all people in the database
// It will return ErrNotFound when there are not people in the database
// Any other errors returned are caused by the underlying database or our connection to it.
func LoadAll() ([]*Person, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return nil, err
}
// perform DB select
rows, err := db.Query(sqlLoadAll)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
var out []*Person
for rows.Next() {
// retrieve columns and populate the person object
record, err := populatePerson(rows.Scan)
if err != nil {
logging.L.Error("failed to convert query result. err: %s", err)
return nil, err
}
out = append(out, record)
}
if len(out) == 0 {
logging.L.Warn("no people found in the database.")
return nil, ErrNotFound
}
return out, nil
}
// Load will attempt to load and return a person.
// It will return ErrNotFound when the requested person does not exist.
// Any other errors returned are caused by the underlying database or our connection to it.
func Load(ID int) (*Person, error) {
db, err := getDB()
if err != nil {
logging.L.Error("failed to get DB connection. err: %s", err)
return nil, err
}
// perform DB select
row := db.QueryRow(sqlLoadByID, ID)
// retrieve columns and populate the person object
out, err := populatePerson(row.Scan)
if err != nil {
if err == sql.ErrNoRows {
logging.L.Warn("failed to load requested person '%d'. err: %s", ID, err)
return nil, ErrNotFound
}
logging.L.Error("failed to convert query result. err: %s", err)
return nil, err
}
return out, nil
}
// custom type so we can convert sql results to easily
type scanner func(dest ...interface{}) error
// reduce the duplication (and maintenance) between sql.Row and sql.Rows usage
func populatePerson(scanner scanner) (*Person, error) {
out := &Person{}
err := scanner(&out.ID, &out.FullName, &out.Phone, &out.Currency, &out.Price)
return out, err
}
func init() {
// ensure the config is loaded and the db initialized
_, _ = getDB()
}
================================================
FILE: ch06/acme/internal/modules/data/data_test.go
================================================
package data
import (
"database/sql"
"errors"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSave_happyPath(t *testing.T) {
// define a mock db
testDb, dbMock, err := sqlmock.New()
defer testDb.Close()
require.NoError(t, err)
// configure the mock db
queryRegex := convertSQLToRegex(sqlInsert)
dbMock.ExpectExec(queryRegex).WillReturnResult(sqlmock.NewResult(2, 1))
// monkey patching starts here
defer func(original sql.DB) {
// restore original DB (after test)
db = &original
}(*db)
// replace db for this test
db = testDb
// end of monkey patch
// inputs
in := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// call function
resultID, err := Save(in)
// validate result
require.NoError(t, err)
assert.Equal(t, 2, resultID)
assert.NoError(t, dbMock.ExpectationsWereMet())
}
func TestSave_insertError(t *testing.T) {
// define a mock db
testDb, dbMock, err := sqlmock.New()
defer testDb.Close()
require.NoError(t, err)
// configure the mock db
queryRegex := convertSQLToRegex(sqlInsert)
dbMock.ExpectExec(queryRegex).WillReturnError(errors.New("failed to insert"))
// monkey patching starts here
defer func(original sql.DB) {
// restore original DB (after test)
db = &original
}(*db)
// replace db for this test
db = testDb
// end of monkey patch
// inputs
in := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// call function
resultID, err := Save(in)
// validate result
require.Error(t, err)
assert.Equal(t, defaultPersonID, resultID)
assert.NoError(t, dbMock.ExpectationsWereMet())
}
func TestSave_getDBError(t *testing.T) {
// monkey patching starts here
defer func(original func() (*sql.DB, error)) {
// restore original DB (after test)
getDB = original
}(getDB)
// replace getDB() function for this test
getDB = func() (*sql.DB, error) {
return nil, errors.New("getDB() failed")
}
// end of monkey patch
// inputs
in := &Person{
FullName: "Jake Blues",
Phone: "01234567890",
Currency: "AUD",
Price: 123.45,
}
// call function
resultID, err := Save(in)
require.Error(t, err)
assert.Equal(t, defaultPersonID, resultID)
}
func TestLoadAll_tableDrivenTest(t *testing.T) {
scenarios := []struct {
desc string
configureMockDB func(sqlmock.Sqlmock)
expectedResults []*Person
expectError bool
}{
{
desc: "happy path",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnRows(
sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
AddRow(1, "John", "0123456789", "AUD", 12.34))
},
expectedResults: []*Person{
{
ID: 1,
FullName: "John",
Phone: "0123456789",
Currency: "AUD",
Price: 12.34,
},
},
expectError: false,
},
{
desc: "load error",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
},
expectedResults: nil,
expectError: true,
},
}
for _, scenario := range scenarios {
// define a mock db
testDb, dbMock, err := sqlmock.New()
require.NoError(t, err)
// configure the mock db
scenario.configureMockDB(dbMock)
// monkey patch the db for this test
original := *db
db = testDb
// call function
results, err := LoadAll()
// validate results
assert.Equal(t, scenario.expectedResults, results, scenario.desc)
assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
assert.NoError(t, dbMock.ExpectationsWereMet())
// restore original DB (after test)
db = &original
testDb.Close()
}
}
func TestLoad_tableDrivenTest(t *testing.T) {
scenarios := []struct {
desc string
configureMockDB func(sqlmock.Sqlmock)
expectedResult *Person
expectError bool
}{
{
desc: "happy path",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnRows(
sqlmock.NewRows(strings.Split(sqlAllColumns, ", ")).
AddRow(2, "Paul", "0123456789", "CAD", 23.45))
},
expectedResult: &Person{
ID: 2,
FullName: "Paul",
Phone: "0123456789",
Currency: "CAD",
Price: 23.45,
},
expectError: false,
},
{
desc: "load error",
configureMockDB: func(dbMock sqlmock.Sqlmock) {
queryRegex := convertSQLToRegex(sqlLoadAll)
dbMock.ExpectQuery(queryRegex).WillReturnError(errors.New("something failed"))
},
expectedResult: nil,
expectError: true,
},
}
for _, scenario := range scenarios {
// define a mock db
testDb, dbMock, err := sqlmock.New()
require.NoError(t, err)
// configure the mock db
scenario.configureMockDB(dbMock)
// monkey db for this test
original := *db
db = testDb
// call function
result, err := Load(2)
// validate results
assert.Equal(t, scenario.expectedResult, result, scenario.desc)
assert.Equal(t, scenario.expectError, err != nil, scenario.desc)
assert.NoError(t, dbMock.ExpectationsWereMet())
// restore original DB (after test)
db = &original
testDb.Close()
}
}
// convert SQL string to regex by treating the entire query as a literal
func convertSQLToRegex(in string) string {
return `\Q` + in + `\E`
}
================================================
FILE: ch06/acme/internal/modules/exchange/converter.go
================================================
package exchange
import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"net/http"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/config"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/logging"
)
const (
// request URL for the exchange rate API
urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
// default price that is sent when an error occurs
defaultPrice = 0.0
)
// Converter will convert the base price to the currency supplied
// Note: we are expecting sane inputs and therefore skipping input validation
type Converter struct{}
// Do will perform the conversion
func (c *Converter) Do(basePrice float64, currency string) (float64, error) {
// load rate from the external API
response, err := c.loadRateFromServer(currency)
if err != nil {
return defaultPrice, err
}
// extract rate from response
rate, err := c.extractRate(response, currency)
if err != nil {
return defaultPrice, err
}
// apply rate and round to 2 decimal places
return math.Floor((basePrice/rate)*100) / 100, nil
}
// load rate from the external API
func (c *Converter) loadRateFromServer(currency string) (*http.Response, error) {
// build the request
url := fmt.Sprintf(urlFormat,
config.App.ExchangeRateBaseURL,
config.App.ExchangeRateAPIKey,
currency)
// perform request
response, err := http.Get(url)
if err != nil {
logging.L.Warn("[exchange] failed to load. err: %s", err)
return nil, err
}
if response.StatusCode != http.StatusOK {
err = fmt.Errorf("request failed with code %d", response.StatusCode)
logging.L.Warn("[exchange] %s", err)
return nil, err
}
return response, nil
}
func (c *Converter) extractRate(response *http.Response, currency string) (float64, error) {
defer func() {
_ = response.Body.Close()
}()
// extract data from response
data, err := c.extractResponse(response)
if err != nil {
return defaultPrice, err
}
// pull rate from response data
rate, found := data.Quotes["USD"+currency]
if !found {
err = fmt.Errorf("response did not include expected currency '%s'", currency)
logging.L.Error("[exchange] %s", err)
return defaultPrice, err
}
// happy path
return rate, nil
}
func (c *Converter) extractResponse(response *http.Response) (*apiResponseFormat, error) {
payload, err := ioutil.ReadAll(response.Body)
if err != nil {
logging.L.Error("[exchange] failed to ready response body. err: %s", err)
return nil, err
}
data := &apiResponseFormat{}
err = json.Unmarshal(payload, data)
if err != nil {
logging.L.Error("[exchange] error converting response. err: %s", err)
return nil, err
}
// happy path
return data, nil
}
// the response format from the exchange rate API
type apiResponseFormat struct {
Quotes map[string]float64 `json:"quotes"`
}
================================================
FILE: ch06/acme/internal/modules/get/get.go
================================================
package get
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
)
var (
// error thrown when the requested person is not in the database
errPersonNotFound = errors.New("person not found")
)
// Getter will attempt to load a person.
// It can return an error caused by the data layer or when the requested person is not found
type Getter struct {
}
// Do will perform the get
func (g *Getter) Do(ID int) (*data.Person, error) {
// load person from the data layer
person, err := loader(ID)
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are hiding the implementation details from our users.
return nil, errPersonNotFound
}
return nil, err
}
return person, err
}
// this function as a variable allows us to Monkey Patch during testing
var loader = data.Load
================================================
FILE: ch06/acme/internal/modules/get/go_test.go
================================================
package get
import (
"errors"
"testing"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetter_Do_happyPath(t *testing.T) {
// inputs
ID := 1234
// monkey patch calls to the data package
defer func(original func(ID int) (*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func(ID int) (*data.Person, error) {
result := &data.Person{
ID: 1234,
FullName: "Doug",
}
var resultErr error
return result, resultErr
}
// end of monkey patch
// call method
getter := &Getter{}
person, err := getter.Do(ID)
// validate expectations
require.NoError(t, err)
assert.Equal(t, ID, person.ID)
assert.Equal(t, "Doug", person.FullName)
}
func TestGetter_Do_noSuchPerson(t *testing.T) {
// inputs
ID := 5678
// monkey patch calls to the data package
defer func(original func(ID int) (*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func(ID int) (*data.Person, error) {
var result *data.Person
resultErr := data.ErrNotFound
return result, resultErr
}
// end of monkey patch
// call method
getter := &Getter{}
person, err := getter.Do(ID)
// validate expectations
require.Equal(t, errPersonNotFound, err)
assert.Nil(t, person)
}
func TestGetter_Do_error(t *testing.T) {
// inputs
ID := 1234
// monkey patch calls to the data package
defer func(original func(ID int) (*data.Person, error)) {
// restore original
loader = original
}(loader)
// replace method
loader = func(ID int) (*data.Person, error) {
var result *data.Person
resultErr := errors.New("failed to load person")
return result, resultErr
}
// end of monkey patch
// call method
getter := &Getter{}
person, err := getter.Do(ID)
// validate expectations
require.Error(t, err)
assert.Nil(t, person)
}
================================================
FILE: ch06/acme/internal/modules/list/list.go
================================================
package list
import (
"errors"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
)
var (
// error thrown when there are no people in the database
errPeopleNotFound = errors.New("no people found")
)
// Lister will attempt to load all people in the database.
// It can return an error caused by the data layer
type Lister struct {
}
// Do will load the people from the data layer
func (l *Lister) Do() ([]*data.Person, error) {
// load all people
people, err := l.load()
if err != nil {
return nil, err
}
if len(people) == 0 {
// special processing for 0 people returned
return nil, errPeopleNotFound
}
return people, nil
}
// load all people
func (l *Lister) load() ([]*data.Person, error) {
people, err := loader()
if err != nil {
if err == data.ErrNotFound {
// By converting the error we are encapsulating the implementation details from our users.
return nil, errPeopleNotFound
}
return nil, err
}
return people, nil
}
// this function as a variable allows us to Monkey Patch during testing
var loader = data.LoadAll
================================================
FILE: ch06/acme/internal/modules/list/list_test.go
================================================
package list
import (
"errors"
"testing"
"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/modules/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLister_Do_happyPath(t *testing.T) {
// monkey
gitextract_t7lucnow/
├── .gitignore
├── LICENSE
├── README.md
├── ch01/
│ ├── 01_defining_depenency_injection/
│ │ ├── 01_interface.go
│ │ ├── 02_function_literal.go
│ │ ├── 03_test_without_nfs_test.go
│ │ └── 04_fail_test_without_nfs_test.go
│ └── 02_code_smells/
│ ├── 01_code_bloat/
│ │ └── 01_switch_type.go
│ ├── 02_resistance_to_change/
│ │ └── 01_shotgun_surgey.go
│ ├── 03_wasted_effort/
│ │ ├── 01_excessive_comments.go
│ │ └── 02_complicated_go.go
│ └── 04_tight_coupling/
│ ├── 01_circular_dependencies/
│ │ ├── config/
│ │ │ └── config.go
│ │ └── payment/
│ │ └── currency.go
│ ├── 02_object_orgy.go
│ └── 03_feature_envy.go
├── ch02/
│ ├── 01_single_responsibility_principle/
│ │ ├── 01_responsibility_vs_change.go
│ │ ├── 02_responsibility_vs_change.go
│ │ ├── 03_responsibility_vs_change.go
│ │ ├── 04_long_method.go
│ │ ├── 04_long_method_test.go
│ │ └── 05_srp_method.go
│ ├── 02_open_closed_principle/
│ │ ├── 01_open_closed_failure.go
│ │ ├── 02_open_closed_success.go
│ │ ├── 03_shotgun_surgery.go
│ │ ├── 04_after_shotgun_surgery.go
│ │ ├── 05_composition.go
│ │ ├── 06_handler_struct.go
│ │ └── 07_handler_func.go
│ ├── 03_liskov_substitution_principle/
│ │ ├── 01_violation/
│ │ │ └── example.go
│ │ ├── 02_fixed/
│ │ │ └── example.go
│ │ ├── 03_fixed/
│ │ │ └── example.go
│ │ ├── 04_behaviour.go
│ │ └── 05_behaviour_fixed.go
│ └── 04_interface_segregation_principle/
│ ├── 01_fat_interface.go
│ ├── 02_thin_interface.go
│ ├── 03_repeated_inputs.go
│ ├── 04_repeated_inputs.go
│ ├── 05_repeated_inputs.go
│ └── 06_implicit_interfaces.go
├── ch03/
│ ├── 01_optimizing_for_humans/
│ │ ├── 01_not_so_simple.go
│ │ ├── 02_start_simple.go
│ │ ├── 03_too_abstract.go
│ │ ├── 04_common_concept.go
│ │ ├── 05_boolean_param.go
│ │ ├── 06_hidden_boolean.go
│ │ ├── 07_wide_formatter.go
│ │ ├── 08_thin_formatters.go
│ │ └── 09_extra_config.go
│ ├── 02_unit_tests/
│ │ ├── 01_loader.go
│ │ ├── 02_language_feature.go
│ │ ├── 03_simple_test.go
│ │ ├── 04_test_from_api.go
│ │ ├── 05_repeated_code.go
│ │ ├── 06_tdt.go
│ │ ├── 07_person_loader.go
│ │ ├── 08_stub.go
│ │ ├── 09_stub_tdt.go
│ │ └── 10_mocks.go
│ ├── 03_test_induced_damage/
│ │ ├── 01_io_closer.go
│ │ └── 02_json.go
│ ├── 04_visualizing_dependencies/
│ │ └── depgraph.sh
│ └── fake.go
├── ch04/
│ ├── 01_welcome/
│ │ ├── 01_bad_names.go
│ │ ├── 02_improved_names.go
│ │ ├── 03_long_method.go
│ │ ├── 04_long_method_test.go
│ │ └── 05_short_methods.go
│ ├── 03_known_issues/
│ │ ├── 01_data_and_rest/
│ │ │ └── get_example.go
│ │ └── 02_config_coupling/
│ │ ├── config.go
│ │ └── currency/
│ │ └── currency.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── common_test.go
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch05/
│ ├── 02_advantages/
│ │ ├── 01_function.go
│ │ ├── 02_monkey_patched.go
│ │ ├── 03_injected_lambda.go
│ │ ├── 04_as_object.go
│ │ ├── 05_math_rand.go
│ │ └── 06_math_rand_test.go
│ ├── 03_applying/
│ │ ├── 01_simple_sqlmock_test.go
│ │ └── 02_load.go
│ ├── 04_disadvantages/
│ │ ├── 01_verbose.go
│ │ ├── 02_verbose_test.go
│ │ └── 03_refactored_test.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── common_test.go
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch06/
│ ├── 01_constructor_injection/
│ │ ├── 01_welcome_email.go
│ │ ├── 01_welcome_email_test.go
│ │ ├── 02_mailer_interface.go
│ │ ├── 03_sender_interface.go
│ │ └── 05_duck_typing.go
│ ├── 02_advantages/
│ │ ├── 01_easy_to_implement.go
│ │ ├── 01_easy_to_implement_example_test.go
│ │ ├── 02_easy_to_implement.go
│ │ ├── 02_easy_to_implement_example_test.go
│ │ ├── 03_predictable.go
│ │ ├── 04_predictable.go
│ │ ├── 05_encapsulation.go
│ │ └── 06_encapsulation.go
│ ├── 03_applying/
│ │ ├── 01/
│ │ │ ├── 01_register_handler_before.go
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ └── register/
│ │ │ └── register.go
│ │ ├── 02/
│ │ │ ├── 01_register_handler.go
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ └── register/
│ │ │ └── register.go
│ │ ├── 03/
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ ├── mock_register_model_test.go
│ │ │ └── register_test.go
│ │ ├── 04/
│ │ │ ├── data/
│ │ │ │ └── person.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── register.go
│ │ │ └── register_test.go
│ │ └── 05/
│ │ ├── data/
│ │ │ └── person.go
│ │ ├── fakes.go
│ │ ├── get/
│ │ │ └── getter.go
│ │ ├── list/
│ │ │ └── lister.go
│ │ ├── register/
│ │ │ └── registerer.go
│ │ └── server.go
│ ├── 04_disadvantages/
│ │ ├── 01_lots_of_changes.go
│ │ ├── 02_overuse.go
│ │ ├── 03_non_obvious.go
│ │ ├── 04_non_obvious_example_test.go
│ │ └── 05_constructors.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ ├── fake.go
│ └── pcov-html
├── ch07/
│ ├── 01_method_injection/
│ │ ├── 01_fprint.go
│ │ ├── 02_http_request.go
│ │ ├── 03_fprint.go
│ │ ├── 04_http_request.go
│ │ ├── 05_timestamp_writer_v1.go
│ │ ├── 06_timestamp_writer_v2.go
│ │ └── 07_timestamp_writer_v3.go
│ ├── 02_advantages/
│ │ ├── 01_handler_v1.go
│ │ ├── 02_handler_v2.go
│ │ ├── 03_handler_v3.go
│ │ ├── 04_context_influence.go
│ │ └── 05_person_loader.go
│ ├── 04_disadvantages/
│ │ ├── 01_data_struct.go
│ │ ├── 02_ux_improvement.go
│ │ ├── 03_many_params.go
│ │ └── 04_many_params_v2.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ └── converter.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch08/
│ ├── 01_config_injection/
│ │ ├── 01_long_constructor.go
│ │ ├── 02_by_config_example.go
│ │ └── 03_shared_params.go
│ ├── 02_advantages/
│ │ ├── 01_injected_config/
│ │ │ ├── 01.go
│ │ │ └── 01_test.go
│ │ ├── 02_config_injection/
│ │ │ ├── 02.go
│ │ │ └── 02_test.go
│ │ ├── 03_long_constructor.go
│ │ ├── 04_by_config_example.go
│ │ ├── config/
│ │ │ └── config.go
│ │ ├── logging/
│ │ │ └── logger.go
│ │ └── stats/
│ │ └── stats.go
│ ├── 03_applying/
│ │ ├── 01_define_register_config.go
│ │ ├── 02_register_with_config_injection.go
│ │ ├── 03_model_before_data_changes.go
│ │ ├── 04_test_config_link_to_config_package.go
│ │ ├── 05_result_payload.json
│ │ └── 06_simple_test_server.go
│ ├── 04_disadvantages/
│ │ ├── 01_leaking_details.go
│ │ ├── 02_hiding_details.go
│ │ ├── 03_unclear_lifecycle.go
│ │ ├── 04_clear_lifecycle.go
│ │ └── 05_layers.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── data.go
│ │ │ │ │ └── data_test.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ └── go_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ └── list_test.go
│ │ │ │ └── register/
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch09/
│ ├── 01_jit_injection/
│ │ ├── 01_injecting_db.go
│ │ ├── 01_injecting_db_test.go
│ │ ├── 02_injecting_business_logic.go
│ │ ├── 03_injecting_db_jit.go
│ │ ├── 03_injecting_db_jit_test.go
│ │ └── 04_noop_debugger.go
│ ├── 02_advantages/
│ │ ├── 01_long_constructor.go
│ │ ├── 02_short_constructor.go
│ │ ├── 03_optional_dep_without_jitdi.go
│ │ ├── 04_optional_dep_with_jitdi.go
│ │ ├── 05_loader.go
│ │ ├── 06_global_variable/
│ │ │ └── 06_global_variable.go
│ │ ├── 07_global_variable_jit/
│ │ │ ├── 07_global_variable_jit.go
│ │ │ └── 07_global_variable_jit_test.go
│ │ ├── 08_car_v1.go
│ │ └── 09_car_v2.go
│ ├── 03_applying/
│ │ ├── 01_commands.sh
│ │ ├── 02_coverage.txt
│ │ └── 03_initial_dao.go
│ ├── 04_disadvantages/
│ │ ├── 01_uncertain_init_state.go
│ │ ├── 02_certain_init_state.go
│ │ ├── 03_cpool_slow_constructor.go
│ │ └── 04_get_pool_with_once.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── dao.go
│ │ │ │ │ ├── data.go
│ │ │ │ │ ├── data_test.go
│ │ │ │ │ └── tracker.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ ├── go_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ ├── list_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ └── register/
│ │ │ │ ├── mock_my_saver_test.go
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ └── main.go
│ └── fake.go
├── ch10/
│ ├── 01_intro_to_wire/
│ │ ├── 01_simple/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 02_params/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 03_error/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ └── 04_without_pset/
│ │ ├── main.go
│ │ ├── wire.go
│ │ └── wire_gen.go
│ ├── 02_advantages/
│ │ ├── 01_dig/
│ │ │ └── main.go
│ │ └── 02_instantiation_order/
│ │ ├── handler.go
│ │ ├── injectors.go
│ │ ├── main.go
│ │ ├── model.go
│ │ ├── providers.go
│ │ └── wire_gen.go
│ ├── 03_applying/
│ │ ├── 01_before_config/
│ │ │ └── main.go
│ │ ├── 02_after_config/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 03_after_exchange/
│ │ │ ├── main.go
│ │ │ └── wire.go
│ │ ├── 04_after_model/
│ │ │ ├── main.go
│ │ │ └── wire.go
│ │ ├── 05_after_rest/
│ │ │ ├── main.go
│ │ │ ├── wire.go
│ │ │ └── wire_gen.go
│ │ ├── 06_build_tag.go
│ │ ├── 06_build_tag_inverse.go
│ │ └── 06_main.go
│ ├── 04_disadvantages/
│ │ └── 01_complexity/
│ │ └── main.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── dao.go
│ │ │ │ │ ├── data.go
│ │ │ │ │ ├── data_test.go
│ │ │ │ │ └── tracker.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ ├── go_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ ├── list_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ └── register/
│ │ │ │ ├── mock_my_saver_test.go
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ ├── main.go
│ │ ├── main_test.go
│ │ ├── wire.go
│ │ └── wire_gen.go
│ └── fake.go
├── ch11/
│ ├── 01_di_induced_damage/
│ │ ├── 01_long_param/
│ │ │ └── 01_long_param.go
│ │ ├── 02_long_param/
│ │ │ └── 01_long_param.go
│ │ ├── 03_long_param/
│ │ │ └── 01_long_param.go
│ │ ├── 04_long_param/
│ │ │ ├── 01_long_param.go
│ │ │ └── 01_long_param_test.go
│ │ ├── 05_inject_sql/
│ │ │ └── 01_interface.go
│ │ ├── 06_inject_sql/
│ │ │ ├── 01_interface.go
│ │ │ ├── 02_implementation.go
│ │ │ ├── 02_implementation_test.go
│ │ │ ├── dao.go
│ │ │ ├── data.go
│ │ │ └── data_test.go
│ │ ├── 07_needless_indirection/
│ │ │ └── example_test.go
│ │ ├── 08_needless_indirection/
│ │ │ ├── 01_mux.go
│ │ │ ├── 01_mux_test.go
│ │ │ └── mock_my_mux_test.go
│ │ ├── 09_needless_indirection/
│ │ │ ├── 01_mux.go
│ │ │ └── 01_mux_test.go
│ │ ├── 10_needless_indirection/
│ │ │ ├── 01_mux_e2e.go
│ │ │ └── 01_mux_e2e_test.go
│ │ └── 11_service_locator/
│ │ ├── 01_service_locator.go
│ │ └── 02_usage.go
│ ├── 02_premature_future/
│ │ └── get.go
│ ├── 03_mocking_http_requests/
│ │ ├── converter.go
│ │ ├── converter_test.go
│ │ └── mock_requester_test.go
│ └── acme/
│ ├── internal/
│ │ ├── config/
│ │ │ ├── config.go
│ │ │ └── config_test.go
│ │ ├── logging/
│ │ │ └── logging.go
│ │ ├── modules/
│ │ │ ├── data/
│ │ │ │ ├── dao.go
│ │ │ │ ├── data.go
│ │ │ │ ├── data_test.go
│ │ │ │ └── tracker.go
│ │ │ ├── exchange/
│ │ │ │ ├── converter.go
│ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ └── converter_int_bounday_test.go
│ │ │ ├── get/
│ │ │ │ ├── get.go
│ │ │ │ ├── go_test.go
│ │ │ │ └── mock_my_loader_test.go
│ │ │ ├── list/
│ │ │ │ ├── list.go
│ │ │ │ ├── list_test.go
│ │ │ │ └── mock_my_loader_test.go
│ │ │ └── register/
│ │ │ ├── mock_exchanger_test.go
│ │ │ ├── mock_my_saver_test.go
│ │ │ ├── register.go
│ │ │ └── register_test.go
│ │ └── rest/
│ │ ├── get.go
│ │ ├── get_test.go
│ │ ├── list.go
│ │ ├── list_test.go
│ │ ├── mock_get_model_test.go
│ │ ├── mock_list_model_test.go
│ │ ├── mock_register_model_test.go
│ │ ├── not_found.go
│ │ ├── not_found_test.go
│ │ ├── register.go
│ │ ├── register_test.go
│ │ └── server.go
│ ├── main.go
│ ├── main_test.go
│ ├── wire.go
│ └── wire_gen.go
├── ch12/
│ ├── 01_improvements/
│ │ └── 01_test_logging_test.go
│ ├── 03_testing/
│ │ ├── 01_mock_get_model.go
│ │ ├── 02_coverage_ch04.txt
│ │ ├── 03_coverage_ch11.txt
│ │ ├── 04_coverage_config.htm
│ │ ├── 04_coverage_data.htm
│ │ ├── 04_coverage_exchange.htm
│ │ ├── 04_coverage_get.htm
│ │ ├── 04_coverage_list.htm
│ │ ├── 04_coverage_main.htm
│ │ ├── 04_coverage_register.htm
│ │ └── 04_coverage_rest.htm
│ ├── 04_new_service/
│ │ └── 01_data_with_cache/
│ │ ├── dao.go
│ │ ├── data.go
│ │ └── internal/
│ │ ├── cache/
│ │ │ └── cache.go
│ │ └── logging/
│ │ └── logging.go
│ ├── acme/
│ │ ├── internal/
│ │ │ ├── config/
│ │ │ │ ├── config.go
│ │ │ │ └── config_test.go
│ │ │ ├── logging/
│ │ │ │ └── logging.go
│ │ │ ├── modules/
│ │ │ │ ├── data/
│ │ │ │ │ ├── dao.go
│ │ │ │ │ ├── data.go
│ │ │ │ │ ├── data_test.go
│ │ │ │ │ └── tracker.go
│ │ │ │ ├── exchange/
│ │ │ │ │ ├── converter.go
│ │ │ │ │ ├── converter_ext_bounday_test.go
│ │ │ │ │ └── converter_int_bounday_test.go
│ │ │ │ ├── get/
│ │ │ │ │ ├── get.go
│ │ │ │ │ ├── go_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ ├── list/
│ │ │ │ │ ├── list.go
│ │ │ │ │ ├── list_test.go
│ │ │ │ │ └── mock_my_loader_test.go
│ │ │ │ └── register/
│ │ │ │ ├── mock_exchanger_test.go
│ │ │ │ ├── mock_my_saver_test.go
│ │ │ │ ├── register.go
│ │ │ │ └── register_test.go
│ │ │ └── rest/
│ │ │ ├── get.go
│ │ │ ├── get_test.go
│ │ │ ├── list.go
│ │ │ ├── list_test.go
│ │ │ ├── mock_get_model_test.go
│ │ │ ├── mock_list_model_test.go
│ │ │ ├── mock_register_model_test.go
│ │ │ ├── not_found.go
│ │ │ ├── not_found_test.go
│ │ │ ├── register.go
│ │ │ ├── register_test.go
│ │ │ └── server.go
│ │ ├── main.go
│ │ ├── main_test.go
│ │ ├── wire.go
│ │ └── wire_gen.go
│ └── fake.go
├── default-config.json
├── fake.go
├── resources/
│ └── create.sql
└── vendor/
├── github.com/
│ ├── DATA-DOG/
│ │ └── go-sqlmock/
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── argument.go
│ │ ├── driver.go
│ │ ├── expectations.go
│ │ ├── expectations_before_go18.go
│ │ ├── expectations_go18.go
│ │ ├── result.go
│ │ ├── rows.go
│ │ ├── rows_go18.go
│ │ ├── sqlmock.go
│ │ ├── sqlmock_go18.go
│ │ ├── statement.go
│ │ └── util.go
│ ├── davecgh/
│ │ └── go-spew/
│ │ ├── LICENSE
│ │ └── spew/
│ │ ├── bypass.go
│ │ ├── bypasssafe.go
│ │ ├── common.go
│ │ ├── config.go
│ │ ├── doc.go
│ │ ├── dump.go
│ │ ├── format.go
│ │ └── spew.go
│ ├── go-sql-driver/
│ │ └── mysql/
│ │ ├── AUTHORS
│ │ ├── CHANGELOG.md
│ │ ├── CONTRIBUTING.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── appengine.go
│ │ ├── buffer.go
│ │ ├── collations.go
│ │ ├── connection.go
│ │ ├── connection_go18.go
│ │ ├── const.go
│ │ ├── driver.go
│ │ ├── dsn.go
│ │ ├── errors.go
│ │ ├── fields.go
│ │ ├── infile.go
│ │ ├── packets.go
│ │ ├── result.go
│ │ ├── rows.go
│ │ ├── statement.go
│ │ ├── transaction.go
│ │ ├── utils.go
│ │ ├── utils_go17.go
│ │ └── utils_go18.go
│ ├── google/
│ │ └── wire/
│ │ ├── AUTHORS
│ │ ├── CODE_OF_CONDUCT.md
│ │ ├── CONTRIBUTING.md
│ │ ├── CONTRIBUTORS
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── wire.go
│ ├── gorilla/
│ │ ├── context/
│ │ │ ├── LICENSE
│ │ │ ├── README.md
│ │ │ ├── context.go
│ │ │ └── doc.go
│ │ └── mux/
│ │ ├── ISSUE_TEMPLATE.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── context_gorilla.go
│ │ ├── context_native.go
│ │ ├── doc.go
│ │ ├── middleware.go
│ │ ├── mux.go
│ │ ├── regexp.go
│ │ ├── route.go
│ │ └── test_helpers.go
│ ├── pmezard/
│ │ └── go-difflib/
│ │ ├── LICENSE
│ │ └── difflib/
│ │ └── difflib.go
│ └── stretchr/
│ ├── objx/
│ │ ├── LICENSE.md
│ │ ├── README.md
│ │ ├── accessors.go
│ │ ├── constants.go
│ │ ├── conversions.go
│ │ ├── doc.go
│ │ ├── map.go
│ │ ├── mutations.go
│ │ ├── security.go
│ │ ├── tests.go
│ │ ├── type_specific_codegen.go
│ │ └── value.go
│ └── testify/
│ ├── LICENSE
│ ├── assert/
│ │ ├── assertion_format.go
│ │ ├── assertion_format.go.tmpl
│ │ ├── assertion_forward.go
│ │ ├── assertion_forward.go.tmpl
│ │ ├── assertions.go
│ │ ├── doc.go
│ │ ├── errors.go
│ │ ├── forward_assertions.go
│ │ └── http_assertions.go
│ ├── mock/
│ │ ├── doc.go
│ │ └── mock.go
│ └── require/
│ ├── doc.go
│ ├── forward_requirements.go
│ ├── require.go
│ ├── require.go.tmpl
│ ├── require_forward.go
│ ├── require_forward.go.tmpl
│ └── requirements.go
├── go.uber.org/
│ └── dig/
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── check_license.sh
│ ├── cycle.go
│ ├── dig.go
│ ├── doc.go
│ ├── error.go
│ ├── glide.yaml
│ ├── internal/
│ │ ├── digreflect/
│ │ │ └── func.go
│ │ └── dot/
│ │ └── graph.go
│ ├── param.go
│ ├── result.go
│ ├── stringer.go
│ ├── types.go
│ └── version.go
├── google.golang.org/
│ └── appengine/
│ ├── LICENSE
│ └── cloudsql/
│ ├── cloudsql.go
│ ├── cloudsql_classic.go
│ └── cloudsql_vm.go
└── vendor.json
Showing preview only (319K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3579 symbols across 591 files)
FILE: ch01/01_defining_depenency_injection/01_interface.go
type Saver (line 9) | type Saver interface
function SavePerson (line 14) | func SavePerson(person *Person, saver Saver) error {
type Person (line 33) | type Person struct
method validate (line 39) | func (p *Person) validate() error {
method encode (line 52) | func (p *Person) encode() ([]byte, error) {
FILE: ch01/01_defining_depenency_injection/02_function_literal.go
function LoadPerson (line 10) | func LoadPerson(ID int, decodePerson func(data []byte) *Person) (*Person...
function loadPerson (line 27) | func loadPerson(ID int) ([]byte, error) {
FILE: ch01/01_defining_depenency_injection/03_test_without_nfs_test.go
function TestSavePerson_happyPath (line 10) | func TestSavePerson_happyPath(t *testing.T) {
type mockSaver (line 30) | type mockSaver struct
method Save (line 35) | func (m *mockSaver) Save(data []byte) error {
FILE: ch01/01_defining_depenency_injection/04_fail_test_without_nfs_test.go
function TestSavePerson_nfsAlwaysFails (line 11) | func TestSavePerson_nfsAlwaysFails(t *testing.T) {
FILE: ch01/02_code_smells/01_code_bloat/01_switch_type.go
function AppendValue (line 7) | func AppendValue(buffer []byte, in interface{}) []byte {
FILE: ch01/02_code_smells/02_resistance_to_change/01_shotgun_surgey.go
type Renderer (line 9) | type Renderer struct
method render (line 11) | func (r Renderer) render(name, phone string, output io.Writer) {
type Validator (line 16) | type Validator struct
method validate (line 18) | func (v Validator) validate(name, phone string) error {
type Saver (line 24) | type Saver struct
method Save (line 26) | func (s *Saver) Save(db *sql.DB, name, phone string) {
FILE: ch01/02_code_smells/03_wasted_effort/01_excessive_comments.go
function outputOrderedPeopleA (line 4) | func outputOrderedPeopleA(in []*Person) {
function outputOrderedPeopleB (line 17) | func outputOrderedPeopleB(in []*Person) {
function outputPeople (line 22) | func outputPeople(in []*Person) {
function sortPeople (line 27) | func sortPeople(in []*Person) {
type Person (line 32) | type Person struct
FILE: ch01/02_code_smells/03_wasted_effort/02_complicated_go.go
function d (line 9) | func d(r, v float64, i *image.RGBA, c color.Color) {
FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config/config.go
type Config (line 12) | type Config struct
function Load (line 22) | func Load(filename string) (*Config, error) {
FILE: ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment/currency.go
type Currency (line 12) | type Currency
type Processor (line 15) | type Processor struct
method Pay (line 20) | func (p *Processor) Pay(amount float64) error {
FILE: ch01/02_code_smells/04_tight_coupling/02_object_orgy.go
type PageLoader (line 9) | type PageLoader struct
method LoadPage (line 12) | func (o *PageLoader) LoadPage(url string) ([]byte, error) {
type Fetcher (line 44) | type Fetcher struct
function newFetcher (line 49) | func newFetcher() *Fetcher {
type Cache (line 53) | type Cache struct
method Get (line 57) | func (c *Cache) Get(key string) ([]byte, error) {
method Set (line 62) | func (c *Cache) Set(key string, data []byte) error {
FILE: ch01/02_code_smells/04_tight_coupling/03_feature_envy.go
type searchRequest (line 8) | type searchRequest struct
method validate (line 14) | func (request searchRequest) validate() error {
type searchResults (line 28) | type searchResults struct
function doSearchWithEnvy (line 32) | func doSearchWithEnvy(request searchRequest) ([]searchResults, error) {
function doSearchWithoutEnvy (line 47) | func doSearchWithoutEnvy(request searchRequest) ([]searchResults, error) {
function performSearch (line 56) | func performSearch(request searchRequest) ([]searchResults, error) {
FILE: ch02/01_single_responsibility_principle/01_responsibility_vs_change.go
type CalculatorV1 (line 9) | type CalculatorV1 struct
method Calculate (line 15) | func (c *CalculatorV1) Calculate(path string) error {
method Output (line 21) | func (c *CalculatorV1) Output(writer io.Writer) {
FILE: ch02/01_single_responsibility_principle/02_responsibility_vs_change.go
type CalculatorV2 (line 9) | type CalculatorV2 struct
method Calculate (line 15) | func (c *CalculatorV2) Calculate(path string) error {
method Output (line 21) | func (c CalculatorV2) Output(writer io.Writer) {
method OutputCSV (line 28) | func (c CalculatorV2) OutputCSV(writer io.Writer) {
FILE: ch02/01_single_responsibility_principle/03_responsibility_vs_change.go
type CalculatorV3 (line 9) | type CalculatorV3 struct
method Calculate (line 15) | func (c *CalculatorV3) Calculate(path string) error {
method getData (line 20) | func (c *CalculatorV3) getData() map[string]float64 {
type Printer (line 25) | type Printer interface
type DefaultPrinter (line 29) | type DefaultPrinter struct
method Output (line 34) | func (d *DefaultPrinter) Output(data map[string]float64) {
type CSVPrinter (line 40) | type CSVPrinter struct
method Output (line 45) | func (d *CSVPrinter) Output(data map[string]float64) {
FILE: ch02/01_single_responsibility_principle/04_long_method.go
function loadUserHandlerLong (line 10) | func loadUserHandlerLong(resp http.ResponseWriter, req *http.Request) {
type Person (line 41) | type Person struct
FILE: ch02/01_single_responsibility_principle/04_long_method_test.go
function TestLoadUserHandler (line 14) | func TestLoadUserHandler(t *testing.T) {
function TestMain (line 32) | func TestMain(m *testing.M) {
FILE: ch02/01_single_responsibility_principle/05_srp_method.go
function loadUserHandlerSRP (line 9) | func loadUserHandlerSRP(resp http.ResponseWriter, req *http.Request) {
function extractIDFromRequest (line 25) | func extractIDFromRequest(req *http.Request) (int64, error) {
function loadPersonByID (line 33) | func loadPersonByID(userID int64) (*Person, error) {
function outputPerson (line 44) | func outputPerson(resp http.ResponseWriter, person *Person) {
FILE: ch02/02_open_closed_principle/01_open_closed_failure.go
function BuildOutputOCPFail (line 8) | func BuildOutputOCPFail(response http.ResponseWriter, format string, per...
function outputCSV (line 29) | func outputCSV(writer io.Writer, person Person) error {
function outputJSON (line 35) | func outputJSON(writer io.Writer, person Person) error {
type Person (line 41) | type Person struct
FILE: ch02/02_open_closed_principle/02_open_closed_success.go
function BuildOutputOCPSuccess (line 8) | func BuildOutputOCPSuccess(response http.ResponseWriter, formatter Perso...
type PersonFormatter (line 19) | type PersonFormatter interface
type CSVPersonFormatter (line 24) | type CSVPersonFormatter struct
method Format (line 27) | func (c *CSVPersonFormatter) Format(writer io.Writer, person Person) e...
type JSONPersonFormatter (line 33) | type JSONPersonFormatter struct
method Format (line 36) | func (j *JSONPersonFormatter) Format(writer io.Writer, person Person) ...
FILE: ch02/02_open_closed_principle/03_shotgun_surgery.go
function GetUserHandlerV1 (line 8) | func GetUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
function DeleteUserHandlerV1 (line 25) | func DeleteUserHandlerV1(resp http.ResponseWriter, req *http.Request) {
function loadUser (line 41) | func loadUser(userID int64) interface{} {
function deleteUser (line 46) | func deleteUser(userID int64) {
function outputUser (line 50) | func outputUser(resp http.ResponseWriter, user interface{}) {
FILE: ch02/02_open_closed_principle/04_after_shotgun_surgery.go
function GetUserHandlerV2 (line 10) | func GetUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
function DeleteUserHandlerV2 (line 27) | func DeleteUserHandlerV2(resp http.ResponseWriter, req *http.Request) {
function extractUserID (line 43) | func extractUserID(values url.Values) (int64, error) {
FILE: ch02/02_open_closed_principle/05_composition.go
type rowConverter (line 7) | type rowConverter struct
method populate (line 11) | func (d *rowConverter) populate(in *Person, scan func(dest ...interfac...
type LoadPerson (line 15) | type LoadPerson struct
method ByID (line 20) | func (loader *LoadPerson) ByID(id int) (Person, error) {
method loadFromDB (line 30) | func (loader *LoadPerson) loadFromDB(id int) *sql.Row {
method All (line 40) | func (loader *LoadPerson) All() ([]Person, error) {
method loadAllFromDB (line 58) | func (loader *LoadPerson) loadAllFromDB() *sql.Rows {
type LoadAll (line 35) | type LoadAll struct
FILE: ch02/02_open_closed_principle/06_handler_struct.go
type healthCheckLong (line 8) | type healthCheckLong struct
method ServeHTTP (line 11) | func (h *healthCheckLong) ServeHTTP(resp http.ResponseWriter, _ *http....
function healthCheckLongUsage (line 15) | func healthCheckLongUsage() {
FILE: ch02/02_open_closed_principle/07_handler_func.go
function healthCheckShort (line 8) | func healthCheckShort(resp http.ResponseWriter, _ *http.Request) {
function healthCheckShortUsage (line 12) | func healthCheckShortUsage() {
FILE: ch02/03_liskov_substitution_principle/01_violation/example.go
function Go (line 3) | func Go(vehicle actions) {
type actions (line 13) | type actions interface
type Vehicle (line 18) | type Vehicle struct
method drive (line 21) | func (v Vehicle) drive() {
method startEngine (line 25) | func (v Vehicle) startEngine() {
method stopEngine (line 29) | func (v Vehicle) stopEngine() {
type Car (line 33) | type Car struct
type Sled (line 37) | type Sled struct
method startEngine (line 41) | func (s Sled) startEngine() {
method stopEngine (line 45) | func (s Sled) stopEngine() {
method pushStart (line 49) | func (s Sled) pushStart() {
FILE: ch02/03_liskov_substitution_principle/02_fixed/example.go
function Go (line 3) | func Go(vehicle actions) {
type actions (line 15) | type actions interface
type poweredActions (line 19) | type poweredActions interface
type unpoweredActions (line 25) | type unpoweredActions interface
type Vehicle (line 30) | type Vehicle struct
method drive (line 33) | func (v Vehicle) drive() {
type PoweredVehicle (line 37) | type PoweredVehicle struct
method startEngine (line 41) | func (v PoweredVehicle) startEngine() {
type Car (line 45) | type Car struct
type Sled (line 49) | type Sled struct
method pushStart (line 53) | func (s Sled) pushStart() {
FILE: ch02/03_liskov_substitution_principle/03_fixed/example.go
function Go (line 3) | func Go(vehicle actions) {
type actions (line 8) | type actions interface
type Car (line 13) | type Car struct
method start (line 17) | func (c Car) start() {
method drive (line 21) | func (c Car) drive() {
type poweredVehicle (line 25) | type poweredVehicle struct
method startEngine (line 28) | func (p poweredVehicle) startEngine() {
type Sled (line 32) | type Sled struct
method start (line 35) | func (s Sled) start() {
method drive (line 39) | func (s Sled) drive() {
FILE: ch02/03_liskov_substitution_principle/04_behaviour.go
type Collection (line 3) | type Collection interface
type CollectionImpl (line 8) | type CollectionImpl struct
method Add (line 12) | func (c *CollectionImpl) Add(item interface{}) {
method Get (line 16) | func (c *CollectionImpl) Get(index int) interface{} {
type ReadOnlyCollection (line 20) | type ReadOnlyCollection struct
method Add (line 24) | func (ro *ReadOnlyCollection) Add(item interface{}) {
FILE: ch02/03_liskov_substitution_principle/05_behaviour_fixed.go
type ImmutableCollection (line 3) | type ImmutableCollection interface
type MutableCollection (line 7) | type MutableCollection interface
type ReadOnlyCollectionV2 (line 12) | type ReadOnlyCollectionV2 struct
method Get (line 16) | func (ro *ReadOnlyCollectionV2) Get(index int) interface{} {
type CollectionImplV2 (line 20) | type CollectionImplV2 struct
method Add (line 24) | func (c *CollectionImplV2) Add(item interface{}) {
FILE: ch02/04_interface_segregation_principle/01_fat_interface.go
type Item (line 7) | type Item struct
type FatDbInterface (line 12) | type FatDbInterface interface
type Cache (line 35) | type Cache struct
method Get (line 39) | func (c *Cache) Get(key string) interface{} {
method Set (line 49) | func (c *Cache) Set(key string, value interface{}) {
FILE: ch02/04_interface_segregation_principle/02_thin_interface.go
type myDB (line 3) | type myDB interface
type CacheV2 (line 8) | type CacheV2 struct
method Get (line 12) | func (c *CacheV2) Get(key string) interface{} {
method Set (line 22) | func (c *CacheV2) Set(key string, value interface{}) {
FILE: ch02/04_interface_segregation_principle/03_repeated_inputs.go
function Encrypt (line 8) | func Encrypt(ctx context.Context, data []byte) ([]byte, error) {
function performEncryption (line 41) | func performEncryption(key []byte, data []byte) []byte {
FILE: ch02/04_interface_segregation_principle/04_repeated_inputs.go
type Value (line 7) | type Value interface
type Monitor (line 11) | type Monitor interface
function EncryptV2 (line 15) | func EncryptV2(keyValue Value, monitor Monitor, data []byte) ([]byte, er...
FILE: ch02/04_interface_segregation_principle/05_repeated_inputs.go
function UseEncryptV2 (line 7) | func UseEncryptV2() {
FILE: ch02/04_interface_segregation_principle/06_implicit_interfaces.go
type Talker (line 7) | type Talker interface
type Dog (line 11) | type Dog struct
method SayHello (line 14) | func (d Dog) SayHello() string {
function Speak (line 18) | func Speak() {
FILE: ch03/01_optimizing_for_humans/01_not_so_simple.go
function NotSoSimple (line 9) | func NotSoSimple(ID int64, name string, age int, registered bool) string {
FILE: ch03/01_optimizing_for_humans/02_start_simple.go
function Simpler (line 8) | func Simpler(ID int64, name string, age int, registered bool) string {
FILE: ch03/01_optimizing_for_humans/03_too_abstract.go
type myGetter (line 8) | type myGetter interface
function TooAbstract (line 12) | func TooAbstract(getter myGetter, url string) ([]byte, error) {
FILE: ch03/01_optimizing_for_humans/04_common_concept.go
function CommonConcept (line 8) | func CommonConcept(url string) ([]byte, error) {
FILE: ch03/01_optimizing_for_humans/05_boolean_param.go
type Pet (line 7) | type Pet struct
function NewPet (line 13) | func NewPet(name string, isDog bool) Pet {
function CreatePetsV1 (line 21) | func CreatePetsV1() {
FILE: ch03/01_optimizing_for_humans/06_hidden_boolean.go
constant isDog (line 4) | isDog = true
constant isCat (line 5) | isCat = false
function NewDog (line 8) | func NewDog(name string) Pet {
function NewCat (line 12) | func NewCat(name string) Pet {
function CreatePetsV2 (line 16) | func CreatePetsV2() {
FILE: ch03/01_optimizing_for_humans/07_wide_formatter.go
type WideFormatter (line 3) | type WideFormatter interface
FILE: ch03/01_optimizing_for_humans/08_thin_formatters.go
type ThinFormatter (line 3) | type ThinFormatter interface
type CSVFormatter (line 7) | type CSVFormatter struct
method Format (line 9) | func (f CSVFormatter) Format(pets []Pet) ([]byte, error) {
type GOBFormatter (line 14) | type GOBFormatter struct
method Format (line 16) | func (f GOBFormatter) Format(pets []Pet) ([]byte, error) {
type JSONFormatter (line 21) | type JSONFormatter struct
method Format (line 23) | func (f JSONFormatter) Format(pets []Pet) ([]byte, error) {
FILE: ch03/01_optimizing_for_humans/09_extra_config.go
function PetFetcher (line 6) | func PetFetcher(search string, limit int, offset int, sortBy string, sor...
function PetFetcherTypicalUsage (line 10) | func PetFetcherTypicalUsage() {
FILE: ch03/02_unit_tests/01_loader.go
type Loader (line 13) | type Loader interface
function TestLoadAndPrint_happyPath (line 17) | func TestLoadAndPrint_happyPath(t *testing.T) {
function TestLoadAndPrint_notFound (line 23) | func TestLoadAndPrint_notFound(t *testing.T) {
function TestLoadAndPrint_error (line 29) | func TestLoadAndPrint_error(t *testing.T) {
function LoadAndPrint (line 35) | func LoadAndPrint(loader Loader, ID int, dest io.Writer) {
type happyPathLoader (line 51) | type happyPathLoader struct
method Load (line 54) | func (l *happyPathLoader) Load(ID int) (*Pet, error) {
type missingLoader (line 59) | type missingLoader struct
method Load (line 62) | func (l *missingLoader) Load(ID int) (*Pet, error) {
type errorLoader (line 67) | type errorLoader struct
method Load (line 70) | func (l *errorLoader) Load(ID int) (*Pet, error) {
FILE: ch03/02_unit_tests/02_language_feature.go
type Pet (line 9) | type Pet struct
function NewPet (line 13) | func NewPet(name string) *Pet {
function TestLanguageFeatures (line 19) | func TestLanguageFeatures(t *testing.T) {
FILE: ch03/02_unit_tests/03_simple_test.go
function concat (line 9) | func concat(a, b string) string {
function TestTooSimple (line 13) | func TestTooSimple(t *testing.T) {
FILE: ch03/02_unit_tests/04_test_from_api.go
type PetSaver (line 7) | type PetSaver struct
method Save (line 10) | func (p PetSaver) Save(pet Pet) (int, error) {
method validate (line 25) | func (p PetSaver) validate(pet Pet) error {
method save (line 30) | func (p PetSaver) save(pet Pet) (sql.Result, error) {
method extractID (line 35) | func (p PetSaver) extractID(result sql.Result) (int, error) {
FILE: ch03/02_unit_tests/05_repeated_code.go
function Round (line 10) | func Round(in float64) int {
function TestRound_down (line 14) | func TestRound_down(t *testing.T) {
function TestRound_up (line 22) | func TestRound_up(t *testing.T) {
function TestRound_noChange (line 30) | func TestRound_noChange(t *testing.T) {
FILE: ch03/02_unit_tests/06_tdt.go
function TestRound (line 9) | func TestRound(t *testing.T) {
FILE: ch03/02_unit_tests/07_person_loader.go
type Person (line 9) | type Person struct
type PersonLoader (line 14) | type PersonLoader interface
function LoadPersonName (line 18) | func LoadPersonName(loader PersonLoader, ID int) (string, error) {
FILE: ch03/02_unit_tests/08_stub.go
type PersonLoaderStub (line 4) | type PersonLoaderStub struct
method Load (line 9) | func (p *PersonLoaderStub) Load(ID int) (*Person, error) {
FILE: ch03/02_unit_tests/09_stub_tdt.go
function TestLoadPersonNameStubs (line 10) | func TestLoadPersonNameStubs(t *testing.T) {
FILE: ch03/02_unit_tests/10_mocks.go
function TestLoadPersonName (line 11) | func TestLoadPersonName(t *testing.T) {
type PersonLoaderMock (line 66) | type PersonLoaderMock struct
method Load (line 70) | func (p *PersonLoaderMock) Load(ID int) (*Person, error) {
FILE: ch03/03_test_induced_damage/01_io_closer.go
function WriteAndClose (line 7) | func WriteAndClose(destination io.WriteCloser, contents string) error {
FILE: ch03/03_test_induced_damage/02_json.go
function PrintAsJSON (line 8) | func PrintAsJSON(destination io.Writer, plant Plant) error {
type Plant (line 18) | type Plant struct
FILE: ch03/fake.go
function init (line 3) | func init() {
FILE: ch04/01_welcome/01_bad_names.go
type HouseV1 (line 3) | type HouseV1 struct
FILE: ch04/01_welcome/02_improved_names.go
type HouseV2 (line 3) | type HouseV2 struct
FILE: ch04/01_welcome/03_long_method.go
function longMethod (line 10) | func longMethod(resp http.ResponseWriter, req *http.Request) {
type Person (line 41) | type Person struct
FILE: ch04/01_welcome/04_long_method_test.go
function TestLongMethod_happyPath (line 15) | func TestLongMethod_happyPath(t *testing.T) {
FILE: ch04/01_welcome/05_short_methods.go
function shortMethods (line 9) | func shortMethods(resp http.ResponseWriter, req *http.Request) {
function extractUserID (line 25) | func extractUserID(req *http.Request) (int64, error) {
function loadPerson (line 34) | func loadPerson(userID int64) (*Person, error) {
function outputPerson (line 45) | func outputPerson(resp http.ResponseWriter, person *Person) {
FILE: ch04/03_known_issues/01_data_and_rest/get_example.go
method writeJSON (line 13) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) er...
FILE: ch04/03_known_issues/02_config_coupling/config.go
type Config (line 7) | type Config struct
FILE: ch04/03_known_issues/02_config_coupling/currency/currency.go
type Currency (line 9) | type Currency
method UnmarshalJSON (line 12) | func (c *Currency) UnmarshalJSON(in []byte) error {
constant AUD (line 30) | AUD = Currency("AUD")
constant CNY (line 31) | CNY = Currency("CNY")
constant EUR (line 32) | EUR = Currency("EUR")
constant USD (line 33) | USD = Currency("USD")
FILE: ch04/acme/internal/config/config.go
constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 18) | type Config struct
function init (line 36) | func init() {
function load (line 46) | func load(filename string) error {
FILE: ch04/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch04/acme/internal/logging/logging.go
type LoggerStdOut (line 11) | type LoggerStdOut struct
method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch04/acme/internal/modules/data/data.go
constant defaultPersonID (line 15) | defaultPersonID = 0
function getDB (line 25) | func getDB() (*sql.DB, error) {
type Person (line 43) | type Person struct
function Save (line 58) | func Save(in *Person) (int, error) {
function LoadAll (line 85) | func LoadAll() ([]*Person, error) {
function Load (line 126) | func Load(ID int) (*Person, error) {
type scanner (line 152) | type scanner
function populatePerson (line 155) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch04/acme/internal/modules/data/data_test.go
function TestData_happyPath (line 10) | func TestData_happyPath(t *testing.T) {
FILE: ch04/acme/internal/modules/exchange/converter.go
constant urlFormat (line 16) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 19) | defaultPrice = 0.0
type Converter (line 24) | type Converter struct
method Do (line 27) | func (c *Converter) Do(basePrice float64, currency string) (float64, e...
method loadRateFromServer (line 45) | func (c *Converter) loadRateFromServer(currency string) (*http.Respons...
method extractRate (line 68) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 91) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
type apiResponseFormat (line 110) | type apiResponseFormat struct
FILE: ch04/acme/internal/modules/get/get.go
type Getter (line 16) | type Getter struct
method Do (line 20) | func (g *Getter) Do(ID int) (*data.Person, error) {
FILE: ch04/acme/internal/modules/get/go_test.go
function TestGetter_Do (line 10) | func TestGetter_Do(t *testing.T) {
FILE: ch04/acme/internal/modules/list/list.go
type Lister (line 16) | type Lister struct
method Do (line 20) | func (l *Lister) Do() ([]*data.Person, error) {
method load (line 36) | func (l *Lister) load() ([]*data.Person, error) {
FILE: ch04/acme/internal/modules/list/list_test.go
function TestLister_Do (line 10) | func TestLister_Do(t *testing.T) {
FILE: ch04/acme/internal/modules/register/register.go
constant defaultPersonID (line 14) | defaultPersonID = 0
type Registerer (line 43) | type Registerer struct
method Do (line 47) | func (r *Registerer) Do(in *data.Person) (int, error) {
method validateInput (line 72) | func (r *Registerer) validateInput(in *data.Person) error {
method getPrice (line 92) | func (r *Registerer) getPrice(currency string) (float64, error) {
method save (line 104) | func (r *Registerer) save(in *data.Person, price float64) (int, error) {
FILE: ch04/acme/internal/modules/register/register_test.go
function TestRegisterer_Do (line 11) | func TestRegisterer_Do(t *testing.T) {
FILE: ch04/acme/internal/rest/common_test.go
function getOpenPort (line 8) | func getOpenPort() (string, error) {
function startServer (line 20) | func startServer(ctx context.Context) (string, error) {
FILE: ch04/acme/internal/rest/get.go
constant defaultPersonID (line 19) | defaultPersonID = 0
type GetHandler (line 26) | type GetHandler struct
method ServeHTTP (line 30) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 57) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 81) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
type getResponseFormat (line 95) | type getResponseFormat struct
FILE: ch04/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 14) | func TestGetHandler_ServeHTTP(t *testing.T) {
FILE: ch04/acme/internal/rest/list.go
type ListHandler (line 14) | type ListHandler struct
method ServeHTTP (line 18) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 37) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
type listResponseFormat (line 54) | type listResponseFormat struct
type listResponseItemFormat (line 58) | type listResponseItemFormat struct
FILE: ch04/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 14) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch04/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch04/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 12) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch04/acme/internal/rest/register.go
type RegisterHandler (line 15) | type RegisterHandler struct
method ServeHTTP (line 19) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 42) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 55) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
type registerRequest (line 67) | type registerRequest struct
FILE: ch04/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 16) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRequest (line 40) | func buildValidRequest() io.Reader {
FILE: ch04/acme/internal/rest/server.go
function New (line 10) | func New(address string) *Server {
type Server (line 21) | type Server struct
method Listen (line 32) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 54) | func (s *Server) buildRouter() http.Handler {
FILE: ch04/acme/main.go
function main (line 10) | func main() {
FILE: ch04/fake.go
function init (line 3) | func init() {
FILE: ch05/02_advantages/01_function.go
function SaveConfig (line 9) | func SaveConfig(filename string, cfg *Config) error {
type Config (line 26) | type Config struct
FILE: ch05/02_advantages/02_monkey_patched.go
function SaveConfigPatched (line 10) | func SaveConfigPatched(filename string, cfg *Config) error {
function SaveConfigPatchedUsage (line 31) | func SaveConfigPatchedUsage() {
FILE: ch05/02_advantages/03_injected_lambda.go
function SaveConfigInjected (line 11) | func SaveConfigInjected(writer fileWriter, filename string, cfg *Config)...
type fileWriter (line 30) | type fileWriter
function SaveConfigInjectedUsage (line 33) | func SaveConfigInjectedUsage() {
FILE: ch05/02_advantages/04_as_object.go
type ConfigSaver (line 11) | type ConfigSaver struct
method Save (line 15) | func (c ConfigSaver) Save(filename string, cfg *Config) error {
function ConfigSaverUsage (line 33) | func ConfigSaverUsage() {
FILE: ch05/02_advantages/05_math_rand.go
type Rand (line 4) | type Rand struct
method Int (line 11) | func (r *Rand) Int() int {
function Int (line 24) | func Int() int { return globalRand.Int() }
function New (line 33) | func New(src Source) *Rand {
type lockedSource (line 40) | type lockedSource struct
method Int63 (line 44) | func (l *lockedSource) Int63() int64 {
type Source (line 51) | type Source interface
FILE: ch05/02_advantages/06_math_rand_test.go
function TestInt (line 9) | func TestInt(t *testing.T) {
type stubSource (line 26) | type stubSource struct
method Int63 (line 29) | func (s *stubSource) Int63() int64 {
FILE: ch05/03_applying/01_simple_sqlmock_test.go
function TestSave_happyPath (line 12) | func TestSave_happyPath(t *testing.T) {
function SavePerson (line 43) | func SavePerson(db *sql.DB, in *Person) (int, error) {
FILE: ch05/03_applying/02_load.go
constant sqlAllColumns (line 15) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlLoadByID (line 16) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
function TestLoad_happyPath (line 19) | func TestLoad_happyPath(t *testing.T) {
function convertSQLToRegex (line 57) | func convertSQLToRegex(in string) string {
function Load (line 64) | func Load(ID int) (*Person, error) {
type Person (line 79) | type Person struct
FILE: ch05/04_disadvantages/01_verbose.go
function SaveConfig (line 9) | func SaveConfig(filename string, cfg *Config) error {
type Config (line 29) | type Config struct
FILE: ch05/04_disadvantages/02_verbose_test.go
function TestSaveConfig (line 10) | func TestSaveConfig(t *testing.T) {
FILE: ch05/04_disadvantages/03_refactored_test.go
function TestSaveConfig_refactored (line 10) | func TestSaveConfig_refactored(t *testing.T) {
function mockWriteFile (line 30) | func mockWriteFile(result error) func(filename string, data []byte, perm...
function restoreWriteFile (line 37) | func restoreWriteFile(original func(filename string, data []byte, perm o...
FILE: ch05/acme/internal/config/config.go
constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 18) | type Config struct
function init (line 36) | func init() {
function load (line 46) | func load(filename string) error {
FILE: ch05/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch05/acme/internal/logging/logging.go
type LoggerStdOut (line 11) | type LoggerStdOut struct
method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch05/acme/internal/modules/data/data.go
constant defaultPersonID (line 15) | defaultPersonID = 0
constant sqlAllColumns (line 18) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 19) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 20) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 21) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Person (line 49) | type Person struct
function Save (line 64) | func Save(in *Person) (int, error) {
function LoadAll (line 91) | func LoadAll() ([]*Person, error) {
function Load (line 131) | func Load(ID int) (*Person, error) {
type scanner (line 156) | type scanner
function populatePerson (line 159) | func populatePerson(scanner scanner) (*Person, error) {
function init (line 165) | func init() {
FILE: ch05/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 14) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 51) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 89) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 116) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 179) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 241) | func convertSQLToRegex(in string) string {
FILE: ch05/acme/internal/modules/exchange/converter.go
constant urlFormat (line 16) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 19) | defaultPrice = 0.0
type Converter (line 24) | type Converter struct
method Do (line 27) | func (c *Converter) Do(basePrice float64, currency string) (float64, e...
method loadRateFromServer (line 45) | func (c *Converter) loadRateFromServer(currency string) (*http.Respons...
method extractRate (line 68) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 91) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
type apiResponseFormat (line 110) | type apiResponseFormat struct
FILE: ch05/acme/internal/modules/get/get.go
type Getter (line 16) | type Getter struct
method Do (line 20) | func (g *Getter) Do(ID int) (*data.Person, error) {
FILE: ch05/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 12) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 44) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 72) | func TestGetter_Do_error(t *testing.T) {
FILE: ch05/acme/internal/modules/list/list.go
type Lister (line 16) | type Lister struct
method Do (line 20) | func (l *Lister) Do() ([]*data.Person, error) {
method load (line 36) | func (l *Lister) load() ([]*data.Person, error) {
FILE: ch05/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 12) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 46) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 71) | func TestLister_Do_error(t *testing.T) {
FILE: ch05/acme/internal/modules/register/register.go
constant defaultPersonID (line 14) | defaultPersonID = 0
type Registerer (line 43) | type Registerer struct
method Do (line 47) | func (r *Registerer) Do(in *data.Person) (int, error) {
method validateInput (line 72) | func (r *Registerer) validateInput(in *data.Person) error {
method getPrice (line 92) | func (r *Registerer) getPrice(currency string) (float64, error) {
method save (line 104) | func (r *Registerer) save(in *data.Person, price float64) (int, error) {
FILE: ch05/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 12) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 44) | func TestRegisterer_Do_error(t *testing.T) {
FILE: ch05/acme/internal/rest/common_test.go
function getOpenPort (line 8) | func getOpenPort() (string, error) {
function startServer (line 20) | func startServer(ctx context.Context) (string, error) {
FILE: ch05/acme/internal/rest/get.go
constant defaultPersonID (line 19) | defaultPersonID = 0
type GetHandler (line 26) | type GetHandler struct
method ServeHTTP (line 30) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 57) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 81) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
type getResponseFormat (line 95) | type getResponseFormat struct
FILE: ch05/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 14) | func TestGetHandler_ServeHTTP(t *testing.T) {
FILE: ch05/acme/internal/rest/list.go
type ListHandler (line 14) | type ListHandler struct
method ServeHTTP (line 18) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 37) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
type listResponseFormat (line 54) | type listResponseFormat struct
type listResponseItemFormat (line 58) | type listResponseItemFormat struct
FILE: ch05/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 14) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch05/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch05/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 12) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch05/acme/internal/rest/register.go
type RegisterHandler (line 15) | type RegisterHandler struct
method ServeHTTP (line 19) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 42) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 55) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
type registerRequest (line 67) | type registerRequest struct
FILE: ch05/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 16) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRequest (line 40) | func buildValidRequest() io.Reader {
FILE: ch05/acme/internal/rest/server.go
function New (line 10) | func New(address string) *Server {
type Server (line 21) | type Server struct
method Listen (line 32) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 54) | func (s *Server) buildRouter() http.Handler {
FILE: ch05/acme/main.go
function main (line 10) | func main() {
FILE: ch05/fake.go
function init (line 3) | func init() {
FILE: ch06/01_constructor_injection/01_welcome_email.go
function NewWelcomeSender (line 7) | func NewWelcomeSender(in *Mailer) (*WelcomeSender, error) {
function NewWelcomeSenderNoGuard (line 18) | func NewWelcomeSenderNoGuard(in *Mailer) *WelcomeSender {
type WelcomeSender (line 25) | type WelcomeSender struct
method Send (line 29) | func (w *WelcomeSender) Send(to string) error {
method buildMessage (line 36) | func (w *WelcomeSender) buildMessage() string {
type Mailer (line 41) | type Mailer struct
method Send (line 48) | func (m *Mailer) Send(to string, body string) error {
method Receive (line 53) | func (m *Mailer) Receive(address string) (string, error) {
FILE: ch06/01_constructor_injection/01_welcome_email_test.go
function TestNewWelcomeSender_happyPath (line 9) | func TestNewWelcomeSender_happyPath(t *testing.T) {
function TestNewWelcomeSender_guardClause (line 15) | func TestNewWelcomeSender_guardClause(t *testing.T) {
function TestNewWelcomeSenderNoGuard_happyPath (line 21) | func TestNewWelcomeSenderNoGuard_happyPath(t *testing.T) {
FILE: ch06/01_constructor_injection/02_mailer_interface.go
type MailerInterface (line 4) | type MailerInterface interface
FILE: ch06/01_constructor_injection/03_sender_interface.go
type Sender (line 3) | type Sender interface
function NewWelcomeSenderV2 (line 7) | func NewWelcomeSenderV2(in Sender) *WelcomeSenderV2 {
type WelcomeSenderV2 (line 14) | type WelcomeSenderV2 struct
method Send (line 18) | func (w *WelcomeSenderV2) Send(to string) error {
method buildMessage (line 25) | func (w *WelcomeSenderV2) buildMessage() string {
FILE: ch06/01_constructor_injection/05_duck_typing.go
type Talker (line 7) | type Talker interface
type Dog (line 12) | type Dog struct
method Speak (line 14) | func (d Dog) Speak() string {
method Shout (line 18) | func (d Dog) Shout() string {
function SpeakExample (line 22) | func SpeakExample() {
FILE: ch06/02_advantages/01_easy_to_implement.go
type WelcomeSender (line 4) | type WelcomeSender struct
method Send (line 8) | func (w *WelcomeSender) Send(to string) error {
method buildMessage (line 15) | func (w *WelcomeSender) buildMessage() string {
type Mailer (line 20) | type Mailer struct
method Send (line 22) | func (m *Mailer) Send(to string, body string) error {
FILE: ch06/02_advantages/01_easy_to_implement_example_test.go
function ExampleWelcomeSender_Send (line 7) | func ExampleWelcomeSender_Send() {
FILE: ch06/02_advantages/02_easy_to_implement.go
function NewWelcomeSenderV2 (line 3) | func NewWelcomeSenderV2(mailer *Mailer) *WelcomeSenderV2 {
type WelcomeSenderV2 (line 10) | type WelcomeSenderV2 struct
method Send (line 14) | func (w *WelcomeSenderV2) Send(to string) error {
method buildMessage (line 21) | func (w *WelcomeSenderV2) buildMessage() string {
FILE: ch06/02_advantages/02_easy_to_implement_example_test.go
function ExampleWelcomeSenderV2_Send (line 7) | func ExampleWelcomeSenderV2_Send() {
FILE: ch06/02_advantages/03_predictable.go
type Engine (line 7) | type Engine interface
type Car (line 15) | type Car struct
method Drive (line 19) | func (c *Car) Drive() error {
method Stop (line 31) | func (c *Car) Stop() error {
FILE: ch06/02_advantages/04_predictable.go
function NewCarV2 (line 7) | func NewCarV2(engine Engine) (*CarV2, error) {
type CarV2 (line 17) | type CarV2 struct
method Drive (line 21) | func (c *CarV2) Drive() error {
method Stop (line 29) | func (c *CarV2) Stop() error {
FILE: ch06/02_advantages/05_encapsulation.go
method FillPetrolTank (line 7) | func (c *CarV2) FillPetrolTank() error {
method fill (line 17) | func (c CarV2) fill() error {
FILE: ch06/02_advantages/06_encapsulation.go
method FillPetrolTankV2 (line 7) | func (c *CarV2) FillPetrolTankV2(engine Engine) error {
FILE: ch06/03_applying/01/01_register_handler_before.go
type RegisterHandler (line 15) | type RegisterHandler struct
method ServeHTTP (line 19) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 42) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 55) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
type registerRequest (line 67) | type registerRequest struct
FILE: ch06/03_applying/01/data/person.go
type Person (line 4) | type Person struct
FILE: ch06/03_applying/01/register/register.go
type Registerer (line 13) | type Registerer struct
method Do (line 17) | func (r *Registerer) Do(in *data.Person) (int, error) {
FILE: ch06/03_applying/02/01_register_handler.go
type RegisterHandler (line 10) | type RegisterHandler struct
FILE: ch06/03_applying/02/data/person.go
type Person (line 4) | type Person struct
FILE: ch06/03_applying/02/register/register.go
type Registerer (line 13) | type Registerer struct
method Do (line 17) | func (r *Registerer) Do(in *data.Person) (int, error) {
FILE: ch06/03_applying/03/data/person.go
type Person (line 4) | type Person struct
FILE: ch06/03_applying/03/mock_register_model_test.go
type MockRegisterModel (line 13) | type MockRegisterModel struct
method Do (line 18) | func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {
FILE: ch06/03_applying/03/register_test.go
function TestRegisterHandler_ServeHTTP (line 8) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
FILE: ch06/03_applying/04/data/person.go
type Person (line 4) | type Person struct
FILE: ch06/03_applying/04/mock_register_model_test.go
type MockRegisterModel (line 13) | type MockRegisterModel struct
method Do (line 18) | func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {
FILE: ch06/03_applying/04/register.go
type RegisterModel (line 10) | type RegisterModel interface
type RegisterHandler (line 17) | type RegisterHandler struct
method ServeHTTP (line 22) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
type registerRequest (line 27) | type registerRequest struct
FILE: ch06/03_applying/04/register_test.go
function TestRegisterHandler_ServeHTTP (line 15) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRequest (line 54) | func buildValidRequest() io.Reader {
FILE: ch06/03_applying/05/data/person.go
type Person (line 4) | type Person struct
FILE: ch06/03_applying/05/fakes.go
function notFoundHandler (line 9) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
type Server (line 16) | type Server struct
function NewGetHandler (line 25) | func NewGetHandler(_ GetModel) *GetHandler {
type GetModel (line 29) | type GetModel interface
type GetHandler (line 33) | type GetHandler struct
method ServeHTTP (line 35) | func (g *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
function NewListHandler (line 37) | func NewListHandler(_ ListModel) *ListHandler {
type ListModel (line 41) | type ListModel interface
type ListHandler (line 45) | type ListHandler struct
method ServeHTTP (line 48) | func (l *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
function NewRegisterHandler (line 50) | func NewRegisterHandler(_ RegisterModel) *RegisterHandler {
type RegisterModel (line 54) | type RegisterModel interface
type RegisterHandler (line 58) | type RegisterHandler struct
method ServeHTTP (line 61) | func (r *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
FILE: ch06/03_applying/05/get/getter.go
type Getter (line 8) | type Getter struct
method Do (line 10) | func (g *Getter) Do(ID int) (*data.Person, error) {
FILE: ch06/03_applying/05/list/lister.go
type Lister (line 8) | type Lister struct
method Do (line 10) | func (l *Lister) Do() ([]*data.Person, error) {
FILE: ch06/03_applying/05/register/registerer.go
type Registerer (line 8) | type Registerer struct
method Do (line 10) | func (r *Registerer) Do(in *data.Person) (int, error) {
FILE: ch06/03_applying/05/server.go
function New (line 10) | func New(address string) *Server {
FILE: ch06/04_disadvantages/01_lots_of_changes.go
function DealCards (line 4) | func DealCards() (player1 []Card, player2 []Card) {
function newDeck (line 22) | func newDeck() []Card {
type Shuffler (line 29) | type Shuffler interface
type Card (line 34) | type Card struct
type myShuffler (line 40) | type myShuffler struct
method Shuffle (line 43) | func (m *myShuffler) Shuffle(cards []Card) {
FILE: ch06/04_disadvantages/02_overuse.go
constant downstreamServer (line 9) | downstreamServer = "http://www.example.com"
type FetchRates (line 12) | type FetchRates struct
method Fetch (line 14) | func (f *FetchRates) Fetch() ([]Rate, error) {
type downstreamResponse (line 48) | type downstreamResponse struct
type Rate (line 52) | type Rate struct
FILE: ch06/04_disadvantages/03_non_obvious.go
function NewClient (line 8) | func NewClient(service DepService) Client {
type Client (line 15) | type Client interface
type clientImpl (line 20) | type clientImpl struct
method DoSomethingUseful (line 24) | func (c *clientImpl) DoSomethingUseful() (bool, error) {
type DepService (line 29) | type DepService interface
FILE: ch06/04_disadvantages/04_non_obvious_example_test.go
function Example (line 3) | func Example() {
type StubClient (line 8) | type StubClient struct
method DoSomethingUseful (line 11) | func (s *StubClient) DoSomethingUseful() (bool, error) {
FILE: ch06/04_disadvantages/05_constructors.go
type InnerService (line 3) | type InnerService struct
function NewInnerService (line 7) | func NewInnerService(innerDep Dependency) *InnerService {
type OuterService (line 13) | type OuterService struct
function NewOuterService (line 20) | func NewOuterService(outerDep Dependency, innerDep Dependency) *OuterSer...
type Dependency (line 28) | type Dependency interface
FILE: ch06/acme/internal/config/config.go
constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 18) | type Config struct
function init (line 36) | func init() {
function load (line 46) | func load(filename string) error {
FILE: ch06/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch06/acme/internal/logging/logging.go
type LoggerStdOut (line 11) | type LoggerStdOut struct
method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch06/acme/internal/modules/data/data.go
constant defaultPersonID (line 15) | defaultPersonID = 0
constant sqlAllColumns (line 18) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 19) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 20) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 21) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Person (line 49) | type Person struct
function Save (line 64) | func Save(in *Person) (int, error) {
function LoadAll (line 91) | func LoadAll() ([]*Person, error) {
function Load (line 131) | func Load(ID int) (*Person, error) {
type scanner (line 156) | type scanner
function populatePerson (line 159) | func populatePerson(scanner scanner) (*Person, error) {
function init (line 165) | func init() {
FILE: ch06/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 14) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 51) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 89) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 116) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 179) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 241) | func convertSQLToRegex(in string) string {
FILE: ch06/acme/internal/modules/exchange/converter.go
constant urlFormat (line 16) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 19) | defaultPrice = 0.0
type Converter (line 24) | type Converter struct
method Do (line 27) | func (c *Converter) Do(basePrice float64, currency string) (float64, e...
method loadRateFromServer (line 45) | func (c *Converter) loadRateFromServer(currency string) (*http.Respons...
method extractRate (line 68) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 91) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
type apiResponseFormat (line 110) | type apiResponseFormat struct
FILE: ch06/acme/internal/modules/get/get.go
type Getter (line 16) | type Getter struct
method Do (line 20) | func (g *Getter) Do(ID int) (*data.Person, error) {
FILE: ch06/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 12) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 44) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 72) | func TestGetter_Do_error(t *testing.T) {
FILE: ch06/acme/internal/modules/list/list.go
type Lister (line 16) | type Lister struct
method Do (line 20) | func (l *Lister) Do() ([]*data.Person, error) {
method load (line 36) | func (l *Lister) load() ([]*data.Person, error) {
FILE: ch06/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 12) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 46) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 71) | func TestLister_Do_error(t *testing.T) {
FILE: ch06/acme/internal/modules/register/register.go
constant defaultPersonID (line 14) | defaultPersonID = 0
type Registerer (line 43) | type Registerer struct
method Do (line 47) | func (r *Registerer) Do(in *data.Person) (int, error) {
method validateInput (line 72) | func (r *Registerer) validateInput(in *data.Person) error {
method getPrice (line 92) | func (r *Registerer) getPrice(currency string) (float64, error) {
method save (line 104) | func (r *Registerer) save(in *data.Person, price float64) (int, error) {
FILE: ch06/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 12) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 44) | func TestRegisterer_Do_error(t *testing.T) {
FILE: ch06/acme/internal/rest/get.go
constant defaultPersonID (line 18) | defaultPersonID = 0
constant muxVarID (line 21) | muxVarID = "id"
type GetModel (line 26) | type GetModel interface
function NewGetHandler (line 31) | func NewGetHandler(model GetModel) *GetHandler {
type GetHandler (line 41) | type GetHandler struct
method ServeHTTP (line 46) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 72) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 96) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
type getResponseFormat (line 110) | type getResponseFormat struct
FILE: ch06/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 17) | func TestGetHandler_ServeHTTP(t *testing.T) {
FILE: ch06/acme/internal/rest/list.go
type ListModel (line 13) | type ListModel interface
function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
type ListHandler (line 26) | type ListHandler struct
method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
type listResponseFormat (line 66) | type listResponseFormat struct
type listResponseItemFormat (line 70) | type listResponseItemFormat struct
FILE: ch06/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch06/acme/internal/rest/mock_get_model_test.go
type MockGetModel (line 13) | type MockGetModel struct
method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {
FILE: ch06/acme/internal/rest/mock_list_model_test.go
type MockListModel (line 13) | type MockListModel struct
method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {
FILE: ch06/acme/internal/rest/mock_register_model_test.go
type MockRegisterModel (line 13) | type MockRegisterModel struct
method Do (line 18) | func (_m *MockRegisterModel) Do(in *data.Person) (int, error) {
FILE: ch06/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch06/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch06/acme/internal/rest/register.go
type RegisterModel (line 13) | type RegisterModel interface
function NewRegisterHandler (line 18) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
type RegisterHandler (line 27) | type RegisterHandler struct
method ServeHTTP (line 32) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 55) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 68) | func (h *RegisterHandler) register(requestPayload *registerRequest) (i...
type registerRequest (line 79) | type registerRequest struct
FILE: ch06/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {
FILE: ch06/acme/internal/rest/server.go
function New (line 10) | func New(address string,
type Server (line 25) | type Server struct
method Listen (line 36) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 58) | func (s *Server) buildRouter() http.Handler {
FILE: ch06/acme/main.go
function main (line 13) | func main() {
FILE: ch06/fake.go
function init (line 3) | func init() {
FILE: ch07/01_method_injection/01_fprint.go
function ExampleA (line 8) | func ExampleA() {
FILE: ch07/01_method_injection/02_http_request.go
function ExampleB (line 9) | func ExampleB() {
FILE: ch07/01_method_injection/03_fprint.go
function Fprint (line 9) | func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
FILE: ch07/01_method_injection/04_http_request.go
function NewRequest (line 10) | func NewRequest(method, url string, body io.Reader) (*http.Request, erro...
function validateMethod (line 40) | func validateMethod(method string) (string, error) {
function validateURL (line 44) | func validateURL(url string) (*url.URL, error) {
FILE: ch07/01_method_injection/05_timestamp_writer_v1.go
function TimeStampWriterV1 (line 10) | func TimeStampWriterV1(writer io.Writer, message string) {
FILE: ch07/01_method_injection/06_timestamp_writer_v2.go
function TimeStampWriterV2 (line 11) | func TimeStampWriterV2(writer io.Writer, message string) error {
FILE: ch07/01_method_injection/07_timestamp_writer_v3.go
function TimeStampWriterV3 (line 11) | func TimeStampWriterV3(writer io.Writer, message string) {
FILE: ch07/02_advantages/01_handler_v1.go
function HandlerV1 (line 8) | func HandlerV1(response http.ResponseWriter, request *http.Request) {
type Animal (line 25) | type Animal struct
FILE: ch07/02_advantages/02_handler_v2.go
function HandlerV2 (line 8) | func HandlerV2(response http.ResponseWriter, request *http.Request) {
function outputAnimal (line 18) | func outputAnimal(response http.ResponseWriter, animal *Animal) {
FILE: ch07/02_advantages/03_handler_v3.go
function HandlerV3 (line 8) | func HandlerV3(response http.ResponseWriter, request *http.Request) {
function outputJSON (line 18) | func outputJSON(response http.ResponseWriter, data interface{}) {
FILE: ch07/02_advantages/04_context_influence.go
function WriteLog (line 9) | func WriteLog(writer io.Writer, message string) error {
function Usage (line 14) | func Usage() {
FILE: ch07/02_advantages/05_person_loader.go
type OrderLoader (line 16) | type OrderLoader interface
function NewLoadOrderHandler (line 21) | func NewLoadOrderHandler(loader OrderLoader) *LoadOrderHandler {
type LoadOrderHandler (line 28) | type LoadOrderHandler struct
method ServeHTTP (line 33) | func (l *LoadOrderHandler) ServeHTTP(response http.ResponseWriter, req...
method authenticateUser (line 115) | func (l *LoadOrderHandler) authenticateUser(request *http.Request) (*U...
method extractOrderID (line 120) | func (l *LoadOrderHandler) extractOrderID(request *http.Request) (int,...
type AuthenticatedLoader (line 68) | type AuthenticatedLoader struct
method loadByOwner (line 74) | func (a *AuthenticatedLoader) loadByOwner(owner Owner, orderID int) (*...
method load (line 89) | func (a *AuthenticatedLoader) load(orderID int) (*Order, error) {
type Owner (line 94) | type Owner interface
type Order (line 98) | type Order struct
type User (line 104) | type User struct
method ID (line 110) | func (u *User) ID() int {
FILE: ch07/04_disadvantages/01_data_struct.go
type PersonLoader (line 9) | type PersonLoader struct
method Load (line 12) | func (d *PersonLoader) Load(db *sql.DB, ID int) (*Person, error) {
method LoadAll (line 16) | func (d *PersonLoader) LoadAll(db *sql.DB) ([]*Person, error) {
type Person (line 20) | type Person struct
FILE: ch07/04_disadvantages/02_ux_improvement.go
type MyPersonLoader (line 3) | type MyPersonLoader interface
FILE: ch07/04_disadvantages/03_many_params.go
type Generator (line 7) | type Generator struct
method Generate (line 9) | func (g *Generator) Generate(storage Storage, template io.Reader, dest...
type Storage (line 13) | type Storage interface
type Renderer (line 17) | type Renderer interface
type Formatter (line 21) | type Formatter interface
FILE: ch07/04_disadvantages/04_many_params_v2.go
function NewGeneratorV2 (line 7) | func NewGeneratorV2(storage Storage, renderer Renderer, formatter Format...
type GeneratorV2 (line 15) | type GeneratorV2 struct
method Generate (line 21) | func (g *GeneratorV2) Generate(template io.Reader, destination io.Writ...
FILE: ch07/acme/internal/config/config.go
constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 18) | type Config struct
function init (line 36) | func init() {
function load (line 46) | func load(filename string) error {
FILE: ch07/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch07/acme/internal/logging/logging.go
type LoggerStdOut (line 11) | type LoggerStdOut struct
method Debug (line 14) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 19) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 24) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 29) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch07/acme/internal/modules/data/data.go
constant defaultPersonID (line 17) | defaultPersonID = 0
constant sqlAllColumns (line 20) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 21) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 22) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 23) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Person (line 51) | type Person struct
function Save (line 66) | func Save(ctx context.Context, in *Person) (int, error) {
function LoadAll (line 97) | func LoadAll(ctx context.Context) ([]*Person, error) {
function Load (line 141) | func Load(ctx context.Context, ID int) (*Person, error) {
type scanner (line 170) | type scanner
function populatePerson (line 173) | func populatePerson(scanner scanner) (*Person, error) {
function init (line 179) | func init() {
FILE: ch07/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 16) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 57) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 99) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 130) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 197) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 263) | func convertSQLToRegex(in string) string {
FILE: ch07/acme/internal/modules/exchange/converter.go
constant urlFormat (line 18) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 21) | defaultPrice = 0.0
type Converter (line 26) | type Converter struct
method Do (line 29) | func (c *Converter) Do(ctx context.Context, basePrice float64, currenc...
method loadRateFromServer (line 47) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
method extractRate (line 84) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 107) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
type apiResponseFormat (line 126) | type apiResponseFormat struct
FILE: ch07/acme/internal/modules/get/get.go
type Getter (line 17) | type Getter struct
method Do (line 21) | func (g *Getter) Do(ID int) (*data.Person, error) {
FILE: ch07/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 45) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 73) | func TestGetter_Do_error(t *testing.T) {
FILE: ch07/acme/internal/modules/list/list.go
type Lister (line 17) | type Lister struct
method Do (line 21) | func (l *Lister) Do() ([]*data.Person, error) {
method load (line 37) | func (l *Lister) load() ([]*data.Person, error) {
FILE: ch07/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 47) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 72) | func TestLister_Do_error(t *testing.T) {
FILE: ch07/acme/internal/modules/register/register.go
constant defaultPersonID (line 15) | defaultPersonID = 0
type Registerer (line 44) | type Registerer struct
method Do (line 48) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
method validateInput (line 73) | func (r *Registerer) validateInput(in *data.Person) error {
method getPrice (line 93) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
method save (line 105) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
FILE: ch07/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 14) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 50) | func TestRegisterer_Do_error(t *testing.T) {
FILE: ch07/acme/internal/rest/get.go
constant defaultPersonID (line 18) | defaultPersonID = 0
constant muxVarID (line 21) | muxVarID = "id"
type GetModel (line 26) | type GetModel interface
function NewGetHandler (line 31) | func NewGetHandler(model GetModel) *GetHandler {
type GetHandler (line 41) | type GetHandler struct
method ServeHTTP (line 46) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 72) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 96) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
type getResponseFormat (line 110) | type getResponseFormat struct
FILE: ch07/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 17) | func TestGetHandler_ServeHTTP(t *testing.T) {
FILE: ch07/acme/internal/rest/list.go
type ListModel (line 13) | type ListModel interface
function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
type ListHandler (line 26) | type ListHandler struct
method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
type listResponseFormat (line 66) | type listResponseFormat struct
type listResponseItemFormat (line 70) | type listResponseItemFormat struct
FILE: ch07/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch07/acme/internal/rest/mock_get_model_test.go
type MockGetModel (line 13) | type MockGetModel struct
method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {
FILE: ch07/acme/internal/rest/mock_list_model_test.go
type MockListModel (line 13) | type MockListModel struct
method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {
FILE: ch07/acme/internal/rest/mock_register_model_test.go
type MockRegisterModel (line 15) | type MockRegisterModel struct
method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...
FILE: ch07/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch07/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch07/acme/internal/rest/register.go
type RegisterModel (line 15) | type RegisterModel interface
function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
type RegisterHandler (line 29) | type RegisterHandler struct
method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
type registerRequest (line 85) | type registerRequest struct
FILE: ch07/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {
FILE: ch07/acme/internal/rest/server.go
function New (line 10) | func New(address string,
type Server (line 25) | type Server struct
method Listen (line 36) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 58) | func (s *Server) buildRouter() http.Handler {
FILE: ch07/acme/main.go
function main (line 13) | func main() {
FILE: ch07/fake.go
function init (line 3) | func init() {
FILE: ch08/01_config_injection/01_long_constructor.go
function NewLongConstructor (line 8) | func NewLongConstructor(logger Logger, stats Instrumentation, limiter Ra...
type MyStruct (line 15) | type MyStruct struct
type Logger (line 19) | type Logger interface
type Instrumentation (line 27) | type Instrumentation interface
type RateLimiter (line 33) | type RateLimiter interface
type Cache (line 39) | type Cache interface
FILE: ch08/01_config_injection/02_by_config_example.go
function NewByConfigConstructor (line 8) | func NewByConfigConstructor(cfg MyConfig, limiter RateLimiter, cache Cac...
type MyConfig (line 15) | type MyConfig interface
FILE: ch08/01_config_injection/03_shared_params.go
function Usage (line 8) | func Usage() {
type FetcherConfig (line 17) | type FetcherConfig interface
function NewFetcher (line 22) | func NewFetcher(cfg FetcherConfig, url string, timeout time.Duration) *M...
type MyObject (line 26) | type MyObject struct
type fakeConfig (line 29) | type fakeConfig struct
method Logger (line 32) | func (f *fakeConfig) Logger() Logger {
method Instrumentation (line 37) | func (f *fakeConfig) Instrumentation() Instrumentation {
method URL (line 41) | func (f *fakeConfig) URL() string {
method Timeout (line 45) | func (f *fakeConfig) Timeout() time.Duration {
FILE: ch08/02_advantages/01_injected_config/01.go
function NewMyObject (line 7) | func NewMyObject(cfg *config.Config) *MyObject {
type MyObject (line 13) | type MyObject struct
method Do (line 17) | func (m *MyObject) Do() (interface{}, error) {
FILE: ch08/02_advantages/01_injected_config/01_test.go
constant testConfigLocation (line 12) | testConfigLocation = ""
function TestInjectedConfig (line 15) | func TestInjectedConfig(t *testing.T) {
FILE: ch08/02_advantages/02_config_injection/02.go
function NewMyObject (line 8) | func NewMyObject(cfg Config) *MyObject {
type Config (line 14) | type Config interface
type MyObject (line 19) | type MyObject struct
method Do (line 23) | func (m *MyObject) Do() (interface{}, error) {
FILE: ch08/02_advantages/02_config_injection/02_test.go
function TestConfigInjection (line 11) | func TestConfigInjection(t *testing.T) {
type TestConfig (line 25) | type TestConfig struct
method Logger (line 30) | func (t *TestConfig) Logger() *logging.Logger {
method Stats (line 34) | func (t *TestConfig) Stats() *stats.Collector {
FILE: ch08/02_advantages/03_long_constructor.go
function NewLongConstructor (line 8) | func NewLongConstructor(logger Logger, stats Instrumentation, limiter Ra...
type MyStruct (line 15) | type MyStruct struct
type Logger (line 19) | type Logger interface
type Instrumentation (line 27) | type Instrumentation interface
type RateLimiter (line 33) | type RateLimiter interface
type Cache (line 39) | type Cache interface
FILE: ch08/02_advantages/04_by_config_example.go
function NewByConfigConstructor (line 4) | func NewByConfigConstructor(cfg MyConfig, url string, credentials string...
type MyConfig (line 11) | type MyConfig interface
FILE: ch08/02_advantages/config/config.go
function LoadFromFile (line 11) | func LoadFromFile(path string) (*Config, error) {
type Config (line 17) | type Config struct
method Logger (line 32) | func (c *Config) Logger() *logging.Logger {
method Stats (line 43) | func (c *Config) Stats() *stats.Collector {
FILE: ch08/02_advantages/logging/logger.go
type Logger (line 4) | type Logger struct
method Error (line 9) | func (l *Logger) Error(message string, args ...interface{}) {
method Warn (line 14) | func (l *Logger) Warn(message string, args ...interface{}) {
method Info (line 19) | func (l *Logger) Info(message string, args ...interface{}) {
method Debug (line 24) | func (l *Logger) Debug(message string, args ...interface{}) {
FILE: ch08/02_advantages/stats/stats.go
type Collector (line 8) | type Collector struct
method Count (line 13) | func (c *Collector) Count(key string, value int) {
method Duration (line 18) | func (c *Collector) Duration(key string, start time.Time) {
FILE: ch08/03_applying/01_define_register_config.go
type Config (line 10) | type Config interface
FILE: ch08/03_applying/02_register_with_config_injection.go
function NewRegisterer (line 13) | func NewRegisterer(cfg Config) *Registerer {
type Config (line 20) | type Config interface
type Registerer (line 31) | type Registerer struct
method getPrice (line 36) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
method logger (line 47) | func (r *Registerer) logger() logging.Logger {
FILE: ch08/03_applying/03_model_before_data_changes.go
type GetterConfig (line 13) | type GetterConfig interface
type Getter (line 19) | type Getter struct
method Do (line 24) | func (g *Getter) Do(ID int) (*data.Person, error) {
FILE: ch08/03_applying/04_test_config_link_to_config_package.go
type testConfig (line 10) | type testConfig struct
method Logger (line 13) | func (t *testConfig) Logger() logging.Logger {
method RegistrationBasePrice (line 18) | func (t *testConfig) RegistrationBasePrice() float64 {
method DataDSN (line 23) | func (t *testConfig) DataDSN() string {
method ExchangeBaseURL (line 28) | func (t *testConfig) ExchangeBaseURL() string {
method ExchangeAPIKey (line 33) | func (t *testConfig) ExchangeAPIKey() string {
FILE: ch08/03_applying/06_simple_test_server.go
type happyExchangeRateService (line 9) | type happyExchangeRateService struct
method ServeHTTP (line 12) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
FILE: ch08/04_disadvantages/01_leaking_details.go
type PeopleFilterConfig (line 8) | type PeopleFilterConfig interface
function PeopleFilter (line 12) | func PeopleFilter(cfg PeopleFilterConfig, filter string) ([]Person, erro...
type PersonLoaderConfig (line 31) | type PersonLoaderConfig interface
type PersonLoader (line 35) | type PersonLoader struct
method LoadAll (line 37) | func (p *PersonLoader) LoadAll(cfg PersonLoaderConfig) ([]Person, erro...
type Person (line 42) | type Person struct
FILE: ch08/04_disadvantages/02_hiding_details.go
type Loader (line 7) | type Loader interface
function PeopleFilterV2 (line 11) | func PeopleFilterV2(loader Loader, filter string) ([]Person, error) {
FILE: ch08/04_disadvantages/03_unclear_lifecycle.go
function DoJob (line 8) | func DoJob(pool WorkerPool, job Job) error {
type WorkerPool (line 25) | type WorkerPool interface
type Worker (line 31) | type Worker interface
type Job (line 36) | type Job interface
FILE: ch08/04_disadvantages/04_clear_lifecycle.go
function DoJobUpdated (line 3) | func DoJobUpdated(pool WorkerPool, job Job) error {
FILE: ch08/04_disadvantages/05_layers.go
function NewLayer1Object (line 3) | func NewLayer1Object(config Layer1Config) *Layer1Object {
type Layer1Config (line 11) | type Layer1Config interface
type Layer1Object (line 16) | type Layer1Object struct
type Layer2Config (line 22) | type Layer2Config interface
type Layer2Object (line 27) | type Layer2Object struct
function NewLayer2Object (line 31) | func NewLayer2Object(config Layer2Config) *Layer2Object {
type Logger (line 38) | type Logger interface
FILE: ch08/acme/internal/config/config.go
constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 18) | type Config struct
method Logger (line 39) | func (c *Config) Logger() logging.Logger {
method RegistrationBasePrice (line 48) | func (c *Config) RegistrationBasePrice() float64 {
method DataDSN (line 53) | func (c *Config) DataDSN() string {
method ExchangeBaseURL (line 58) | func (c *Config) ExchangeBaseURL() string {
method ExchangeAPIKey (line 63) | func (c *Config) ExchangeAPIKey() string {
method BindAddress (line 68) | func (c *Config) BindAddress() string {
function init (line 73) | func init() {
function load (line 83) | func load(filename string) error {
FILE: ch08/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch08/acme/internal/logging/logging.go
type Logger (line 8) | type Logger interface
type LoggerStdOut (line 19) | type LoggerStdOut struct
method Debug (line 22) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 27) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 32) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 37) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch08/acme/internal/modules/data/data.go
constant defaultPersonID (line 16) | defaultPersonID = 0
constant sqlAllColumns (line 19) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 20) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 21) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 22) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Config (line 33) | type Config interface
type Person (line 55) | type Person struct
function Save (line 70) | func Save(ctx context.Context, cfg Config, in *Person) (int, error) {
function LoadAll (line 101) | func LoadAll(ctx context.Context, cfg Config) ([]*Person, error) {
function Load (line 145) | func Load(ctx context.Context, cfg Config, ID int) (*Person, error) {
type scanner (line 174) | type scanner
function populatePerson (line 177) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch08/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 52) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 88) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 119) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 186) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 252) | func convertSQLToRegex(in string) string {
type testConfig (line 256) | type testConfig struct
method Logger (line 259) | func (t *testConfig) Logger() logging.Logger {
method DataDSN (line 264) | func (t *testConfig) DataDSN() string {
FILE: ch08/acme/internal/modules/exchange/converter.go
constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 20) | defaultPrice = 0.0
function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
type Config (line 31) | type Config interface
type Converter (line 39) | type Converter struct
method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
method logger (line 140) | func (c *Converter) logger() logging.Logger {
type apiResponseFormat (line 145) | type apiResponseFormat struct
FILE: ch08/acme/internal/modules/exchange/converter_ext_bounday_test.go
function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {
FILE: ch08/acme/internal/modules/exchange/converter_int_bounday_test.go
function TestInternalBoundaryTest (line 13) | func TestInternalBoundaryTest(t *testing.T) {
type happyExchangeRateService (line 33) | type happyExchangeRateService struct
method ServeHTTP (line 36) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
type testConfig (line 52) | type testConfig struct
method Logger (line 58) | func (t *testConfig) Logger() logging.Logger {
method ExchangeBaseURL (line 63) | func (t *testConfig) ExchangeBaseURL() string {
method ExchangeAPIKey (line 68) | func (t *testConfig) ExchangeAPIKey() string {
FILE: ch08/acme/internal/modules/get/get.go
function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
type Config (line 24) | type Config interface
type Getter (line 31) | type Getter struct
method Do (line 36) | func (g *Getter) Do(ID int) (*data.Person, error) {
FILE: ch08/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 45) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 73) | func TestGetter_Do_error(t *testing.T) {
FILE: ch08/acme/internal/modules/list/list.go
function NewLister (line 17) | func NewLister(cfg Config) *Lister {
type Config (line 24) | type Config interface
type Lister (line 31) | type Lister struct
method Do (line 36) | func (l *Lister) Do() ([]*data.Person, error) {
method load (line 52) | func (l *Lister) load() ([]*data.Person, error) {
FILE: ch08/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 47) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 72) | func TestLister_Do_error(t *testing.T) {
FILE: ch08/acme/internal/modules/register/register.go
constant defaultPersonID (line 13) | defaultPersonID = 0
function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
type Exchanger (line 45) | type Exchanger interface
type Config (line 51) | type Config interface
type Registerer (line 63) | type Registerer struct
method Do (line 69) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
method validateInput (line 94) | func (r *Registerer) validateInput(in *data.Person) error {
method getPrice (line 114) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
method save (line 125) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
method logger (line 135) | func (r *Registerer) logger() logging.Logger {
FILE: ch08/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 15) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 54) | func TestRegisterer_Do_error(t *testing.T) {
type testConfig (line 94) | type testConfig struct
method Logger (line 97) | func (t *testConfig) Logger() logging.Logger {
method RegistrationBasePrice (line 102) | func (t *testConfig) RegistrationBasePrice() float64 {
method DataDSN (line 107) | func (t *testConfig) DataDSN() string {
type stubExchanger (line 111) | type stubExchanger struct
method Exchange (line 114) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...
FILE: ch08/acme/internal/rest/get.go
constant defaultPersonID (line 18) | defaultPersonID = 0
constant muxVarID (line 21) | muxVarID = "id"
type GetModel (line 26) | type GetModel interface
type GetConfig (line 31) | type GetConfig interface
function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
type GetHandler (line 47) | type GetHandler struct
method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
type getResponseFormat (line 117) | type getResponseFormat struct
FILE: ch08/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
type testConfig (line 146) | type testConfig struct
method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
method BindAddress (line 153) | func (*testConfig) BindAddress() string {
FILE: ch08/acme/internal/rest/list.go
type ListModel (line 13) | type ListModel interface
function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
type ListHandler (line 26) | type ListHandler struct
method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
type listResponseFormat (line 66) | type listResponseFormat struct
type listResponseItemFormat (line 70) | type listResponseItemFormat struct
FILE: ch08/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch08/acme/internal/rest/mock_get_model_test.go
type MockGetModel (line 13) | type MockGetModel struct
method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {
FILE: ch08/acme/internal/rest/mock_list_model_test.go
type MockListModel (line 13) | type MockListModel struct
method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {
FILE: ch08/acme/internal/rest/mock_register_model_test.go
type MockRegisterModel (line 15) | type MockRegisterModel struct
method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...
FILE: ch08/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch08/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch08/acme/internal/rest/register.go
type RegisterModel (line 15) | type RegisterModel interface
function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
type RegisterHandler (line 29) | type RegisterHandler struct
method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
type registerRequest (line 85) | type registerRequest struct
FILE: ch08/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {
FILE: ch08/acme/internal/rest/server.go
type Config (line 11) | type Config interface
function New (line 17) | func New(cfg Config,
type Server (line 32) | type Server struct
method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {
FILE: ch08/acme/main.go
function main (line 14) | func main() {
FILE: ch08/fake.go
function init (line 3) | func init() {
FILE: ch09/01_jit_injection/01_injecting_db.go
function NewMyLoadPersonLogic (line 3) | func NewMyLoadPersonLogic(ds DataSource) *MyLoadPersonLogic {
type MyLoadPersonLogic (line 9) | type MyLoadPersonLogic struct
method Load (line 14) | func (m *MyLoadPersonLogic) Load(ID int) (Person, error) {
type DataSource (line 18) | type DataSource interface
type Person (line 23) | type Person struct
FILE: ch09/01_jit_injection/01_injecting_db_test.go
function TestMyLoadPersonLogic (line 9) | func TestMyLoadPersonLogic(t *testing.T) {
type mockDB (line 25) | type mockDB struct
method Load (line 31) | func (m *mockDB) Load(ID int) (Person, error) {
FILE: ch09/01_jit_injection/02_injecting_business_logic.go
function NewLoadPersonHandler (line 8) | func NewLoadPersonHandler(logic LoadPersonLogic) *LoadPersonHandler {
type LoadPersonHandler (line 14) | type LoadPersonHandler struct
method ServeHTTP (line 18) | func (h *LoadPersonHandler) ServeHTTP(response http.ResponseWriter, re...
method extractInputFromRequest (line 31) | func (h *LoadPersonHandler) extractInputFromRequest(request *http.Requ...
method writeOutput (line 36) | func (h *LoadPersonHandler) writeOutput(writer http.ResponseWriter, pe...
type LoadPersonLogic (line 40) | type LoadPersonLogic interface
FILE: ch09/01_jit_injection/03_injecting_db_jit.go
type MyLoadPersonLogicJIT (line 7) | type MyLoadPersonLogicJIT struct
method Load (line 12) | func (m *MyLoadPersonLogicJIT) Load(ID int) (Person, error) {
method getDataSource (line 16) | func (m *MyLoadPersonLogicJIT) getDataSource() DataSourceJIT {
type DataSourceJIT (line 24) | type DataSourceJIT interface
function NewMyDataSourceJIT (line 29) | func NewMyDataSourceJIT() *MyDataSourceJIT {
type MyDataSourceJIT (line 34) | type MyDataSourceJIT struct
method Load (line 37) | func (m *MyDataSourceJIT) Load(ID int) (Person, error) {
FILE: ch09/01_jit_injection/03_injecting_db_jit_test.go
function TestMyLoadPersonLogicJIT (line 9) | func TestMyLoadPersonLogicJIT(t *testing.T) {
FILE: ch09/01_jit_injection/04_noop_debugger.go
type ObjectWithDebugger (line 7) | type ObjectWithDebugger struct
method DoSomethingAmazing (line 11) | func (o *ObjectWithDebugger) DoSomethingAmazing(input int) error {
method getDebugger (line 20) | func (o *ObjectWithDebugger) getDebugger() Debugger {
method doSomething (line 28) | func (o *ObjectWithDebugger) doSomething() error {
type Debugger (line 32) | type Debugger interface
type noopDebugger (line 37) | type noopDebugger struct
method Log (line 42) | func (n *noopDebugger) Log(_ string, args ...interface{}) {
FILE: ch09/02_advantages/01_long_constructor.go
function NewGenerator (line 7) | func NewGenerator(storage Storage, renderer Renderer, template io.Reader...
type Generator (line 15) | type Generator struct
method Generate (line 21) | func (g *Generator) Generate(destination io.Writer, params ...interfac...
type Storage (line 25) | type Storage interface
type Renderer (line 29) | type Renderer interface
FILE: ch09/02_advantages/02_short_constructor.go
function NewGeneratorV2 (line 7) | func NewGeneratorV2(template io.Reader) *Generator {
method getStorage (line 13) | func (g *Generator) getStorage() Storage {
method getRenderer (line 20) | func (g *Generator) getRenderer() Renderer {
type DefaultStorage (line 28) | type DefaultStorage struct
method Load (line 31) | func (d *DefaultStorage) Load() []interface{} {
type DefaultRenderer (line 36) | type DefaultRenderer struct
method Render (line 39) | func (d *DefaultRenderer) Render(template io.Reader, params ...interfa...
FILE: ch09/02_advantages/03_optional_dep_without_jitdi.go
function NewLoaderWithoutJIT (line 3) | func NewLoaderWithoutJIT(ds Datastore) *LoaderWithoutJIT {
type LoaderWithoutJIT (line 9) | type LoaderWithoutJIT struct
method Load (line 17) | func (l *LoaderWithoutJIT) Load(ID int) (*Animal, error) {
type Cache (line 45) | type Cache interface
type Datastore (line 50) | type Datastore interface
type Animal (line 55) | type Animal struct
FILE: ch09/02_advantages/04_optional_dep_with_jitdi.go
function NewLoaderWithJIT (line 3) | func NewLoaderWithJIT(ds Datastore) *LoaderWithJIT {
type LoaderWithJIT (line 9) | type LoaderWithJIT struct
method Load (line 17) | func (l *LoaderWithJIT) Load(ID int) (*Animal, error) {
method cache (line 38) | func (l *LoaderWithJIT) cache() Cache {
type noopCache (line 47) | type noopCache struct
method Get (line 51) | func (n *noopCache) Get(ID int) *Animal {
method Put (line 56) | func (n *noopCache) Put(ID int, value *Animal) {
FILE: ch09/02_advantages/05_loader.go
function NewLoader (line 7) | func NewLoader(ds Datastore, cache Cache) *MyLoader {
type MyLoader (line 14) | type MyLoader struct
method LoadAll (line 19) | func (l *MyLoader) LoadAll() ([]interface{}, error) {
FILE: ch09/02_advantages/06_global_variable/06_global_variable.go
type Saver (line 6) | type Saver struct
method Do (line 9) | func (s *Saver) Do(in *User) error {
method validate (line 18) | func (s *Saver) validate(in *User) error {
type UserStorage (line 23) | type UserStorage interface
type User (line 27) | type User struct
FILE: ch09/02_advantages/07_global_variable_jit/07_global_variable_jit.go
type Saver (line 6) | type Saver struct
method Do (line 10) | func (s *Saver) Do(in *User) error {
method getStorage (line 20) | func (s *Saver) getStorage() UserStorage {
method validate (line 28) | func (s *Saver) validate(in *User) error {
type UserStorage (line 33) | type UserStorage interface
type User (line 37) | type User struct
FILE: ch09/02_advantages/07_global_variable_jit/07_global_variable_jit_test.go
function TestSaver_Do (line 9) | func TestSaver_Do(t *testing.T) {
type StubUserStorage (line 30) | type StubUserStorage struct
method Save (line 32) | func (s *StubUserStorage) Save(_ *User) error {
FILE: ch09/02_advantages/08_car_v1.go
type CarV1 (line 3) | type CarV1 struct
method Drive (line 7) | func (c *CarV1) Drive() {
type Engine (line 14) | type Engine interface
FILE: ch09/02_advantages/09_car_v2.go
type CarV2 (line 3) | type CarV2 struct
method Drive (line 7) | func (c *CarV2) Drive() {
method getEngine (line 16) | func (c *CarV2) getEngine() Engine {
function newEngine (line 24) | func newEngine() Engine {
FILE: ch09/03_applying/03_initial_dao.go
function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
type DAO (line 21) | type DAO struct
method Load (line 28) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
FILE: ch09/04_disadvantages/01_uncertain_init_state.go
type ConnectionPool (line 10) | type ConnectionPool interface
type Sender (line 16) | type Sender struct
method Send (line 22) | func (l *Sender) Send(ctx context.Context, payload []byte) error {
method getConnectionPool (line 45) | func (l *Sender) getConnectionPool() ConnectionPool {
type myConnectionPool (line 58) | type myConnectionPool struct
method IsReady (line 62) | func (m *myConnectionPool) IsReady() <-chan struct{} {
method Get (line 68) | func (m *myConnectionPool) Get() net.Conn {
method Release (line 74) | func (m *myConnectionPool) Release(_ net.Conn) {
method init (line 78) | func (m *myConnectionPool) init() {
FILE: ch09/04_disadvantages/02_certain_init_state.go
method SendWithoutReadyCheck (line 3) | func (l *Sender) SendWithoutReadyCheck(payload []byte) error {
FILE: ch09/04_disadvantages/03_cpool_slow_constructor.go
function newConnectionPool (line 3) | func newConnectionPool() ConnectionPool {
FILE: ch09/04_disadvantages/04_get_pool_with_once.go
method getConnectionPoolOnce (line 3) | func (l *Sender) getConnectionPoolOnce() ConnectionPool {
FILE: ch09/acme/internal/config/config.go
constant DefaultEnvVar (line 12) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 18) | type Config struct
method Logger (line 39) | func (c *Config) Logger() logging.Logger {
method RegistrationBasePrice (line 48) | func (c *Config) RegistrationBasePrice() float64 {
method DataDSN (line 53) | func (c *Config) DataDSN() string {
method ExchangeBaseURL (line 58) | func (c *Config) ExchangeBaseURL() string {
method ExchangeAPIKey (line 63) | func (c *Config) ExchangeAPIKey() string {
method BindAddress (line 68) | func (c *Config) BindAddress() string {
function init (line 73) | func init() {
function load (line 83) | func load(filename string) error {
FILE: ch09/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch09/acme/internal/logging/logging.go
type Logger (line 8) | type Logger interface
type LoggerStdOut (line 19) | type LoggerStdOut struct
method Debug (line 22) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 27) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 32) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 37) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch09/acme/internal/modules/data/dao.go
function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
type DAO (line 21) | type DAO struct
method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {
FILE: ch09/acme/internal/modules/data/data.go
constant defaultPersonID (line 13) | defaultPersonID = 0
constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 17) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 18) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 19) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Config (line 30) | type Config interface
type Person (line 52) | type Person struct
type scanner (line 66) | type scanner
function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch09/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
type testConfig (line 261) | type testConfig struct
method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
method DataDSN (line 269) | func (t *testConfig) DataDSN() string {
FILE: ch09/acme/internal/modules/data/tracker.go
type QueryTracker (line 10) | type QueryTracker interface
type noopTracker (line 16) | type noopTracker struct
method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
type LogTracker (line 31) | type LogTracker struct
method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {
FILE: ch09/acme/internal/modules/exchange/converter.go
constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 20) | defaultPrice = 0.0
function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
type Config (line 31) | type Config interface
type Converter (line 39) | type Converter struct
method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
method logger (line 140) | func (c *Converter) logger() logging.Logger {
type apiResponseFormat (line 145) | type apiResponseFormat struct
FILE: ch09/acme/internal/modules/exchange/converter_ext_bounday_test.go
function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {
FILE: ch09/acme/internal/modules/exchange/converter_int_bounday_test.go
function TestInternalBoundaryTest (line 13) | func TestInternalBoundaryTest(t *testing.T) {
type happyExchangeRateService (line 33) | type happyExchangeRateService struct
method ServeHTTP (line 36) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
type testConfig (line 52) | type testConfig struct
method Logger (line 58) | func (t *testConfig) Logger() logging.Logger {
method ExchangeBaseURL (line 63) | func (t *testConfig) ExchangeBaseURL() string {
method ExchangeAPIKey (line 68) | func (t *testConfig) ExchangeAPIKey() string {
FILE: ch09/acme/internal/modules/get/get.go
function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
type Config (line 24) | type Config interface
type Getter (line 31) | type Getter struct
method Do (line 37) | func (g *Getter) Do(ID int) (*data.Person, error) {
method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
type myLoader (line 60) | type myLoader interface
FILE: ch09/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {
FILE: ch09/acme/internal/modules/get/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...
FILE: ch09/acme/internal/modules/list/list.go
function NewLister (line 17) | func NewLister(cfg Config) *Lister {
type Config (line 24) | type Config interface
type Lister (line 31) | type Lister struct
method Do (line 37) | func (l *Lister) Do() ([]*data.Person, error) {
method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
type myLoader (line 78) | type myLoader interface
FILE: ch09/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {
FILE: ch09/acme/internal/modules/list/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...
FILE: ch09/acme/internal/modules/register/mock_my_saver_test.go
type mockMySaver (line 15) | type mockMySaver struct
method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...
FILE: ch09/acme/internal/modules/register/register.go
constant defaultPersonID (line 13) | defaultPersonID = 0
function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
type Exchanger (line 45) | type Exchanger interface
type Config (line 51) | type Config interface
type Registerer (line 63) | type Registerer struct
method Do (line 70) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
method validateInput (line 95) | func (r *Registerer) validateInput(in *data.Person) error {
method getPrice (line 115) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
method save (line 126) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
method getSaver (line 136) | func (r *Registerer) getSaver() mySaver {
method logger (line 144) | func (r *Registerer) logger() logging.Logger {
type mySaver (line 149) | type mySaver interface
FILE: ch09/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 16) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 48) | func TestRegisterer_Do_error(t *testing.T) {
type testConfig (line 79) | type testConfig struct
method Logger (line 82) | func (t *testConfig) Logger() logging.Logger {
method RegistrationBasePrice (line 87) | func (t *testConfig) RegistrationBasePrice() float64 {
method DataDSN (line 92) | func (t *testConfig) DataDSN() string {
type stubExchanger (line 96) | type stubExchanger struct
method Exchange (line 99) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...
FILE: ch09/acme/internal/rest/get.go
constant defaultPersonID (line 18) | defaultPersonID = 0
constant muxVarID (line 21) | muxVarID = "id"
type GetModel (line 26) | type GetModel interface
type GetConfig (line 31) | type GetConfig interface
function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
type GetHandler (line 47) | type GetHandler struct
method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
type getResponseFormat (line 117) | type getResponseFormat struct
FILE: ch09/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
type testConfig (line 146) | type testConfig struct
method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
method BindAddress (line 153) | func (*testConfig) BindAddress() string {
FILE: ch09/acme/internal/rest/list.go
type ListModel (line 13) | type ListModel interface
function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
type ListHandler (line 26) | type ListHandler struct
method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
type listResponseFormat (line 66) | type listResponseFormat struct
type listResponseItemFormat (line 70) | type listResponseItemFormat struct
FILE: ch09/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch09/acme/internal/rest/mock_get_model_test.go
type MockGetModel (line 13) | type MockGetModel struct
method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {
FILE: ch09/acme/internal/rest/mock_list_model_test.go
type MockListModel (line 13) | type MockListModel struct
method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {
FILE: ch09/acme/internal/rest/mock_register_model_test.go
type MockRegisterModel (line 15) | type MockRegisterModel struct
method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...
FILE: ch09/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch09/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch09/acme/internal/rest/register.go
type RegisterModel (line 15) | type RegisterModel interface
function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
type RegisterHandler (line 29) | type RegisterHandler struct
method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
type registerRequest (line 85) | type registerRequest struct
FILE: ch09/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {
FILE: ch09/acme/internal/rest/server.go
type Config (line 11) | type Config interface
function New (line 17) | func New(cfg Config,
type Server (line 32) | type Server struct
method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {
FILE: ch09/acme/main.go
function main (line 14) | func main() {
FILE: ch09/fake.go
function init (line 3) | func init() {
FILE: ch10/01_intro_to_wire/01_simple/main.go
function main (line 10) | func main() {
function ProvideFetcher (line 21) | func ProvideFetcher() *Fetcher {
type Fetcher (line 26) | type Fetcher struct
method GoFetch (line 29) | func (f *Fetcher) GoFetch() (string, error) {
FILE: ch10/01_intro_to_wire/01_simple/wire.go
function initializeDeps (line 11) | func initializeDeps() *Fetcher {
FILE: ch10/01_intro_to_wire/01_simple/wire_gen.go
function initializeDeps (line 10) | func initializeDeps() *Fetcher {
FILE: ch10/01_intro_to_wire/02_params/main.go
function main (line 10) | func main() {
function ProvideFetcher (line 21) | func ProvideFetcher(cache *Cache) *Fetcher {
function ProvideCache (line 27) | func ProvideCache() *Cache {
type Cache (line 31) | type Cache struct
method Get (line 33) | func (c *Cache) Get(key string) (string, error) {
method Set (line 37) | func (c *Cache) Set(key string, value string) error {
type Fetcher (line 41) | type Fetcher struct
method GoFetch (line 45) | func (f *Fetcher) GoFetch() (string, error) {
FILE: ch10/01_intro_to_wire/02_params/wire.go
function initializeDeps (line 11) | func initializeDeps() *Fetcher {
FILE: ch10/01_intro_to_wire/02_params/wire_gen.go
function initializeDeps (line 10) | func initializeDeps() *Fetcher {
FILE: ch10/01_intro_to_wire/03_error/main.go
function main (line 10) | func main() {
function ProvideFetcher (line 24) | func ProvideFetcher(cache *Cache) *Fetcher {
function ProvideCache (line 30) | func ProvideCache() (*Cache, error) {
type Cache (line 41) | type Cache struct
method Start (line 43) | func (c *Cache) Start() error {
method Get (line 47) | func (c *Cache) Get(key string) (string, error) {
method Set (line 51) | func (c *Cache) Set(key string, value string) error {
type Fetcher (line 55) | type Fetcher struct
method GoFetch (line 59) | func (f *Fetcher) GoFetch() (string, error) {
FILE: ch10/01_intro_to_wire/03_error/wire.go
function initializeDeps (line 11) | func initializeDeps() (*Fetcher, error) {
FILE: ch10/01_intro_to_wire/03_error/wire_gen.go
function initializeDeps (line 10) | func initializeDeps() (*Fetcher, error) {
FILE: ch10/01_intro_to_wire/04_without_pset/main.go
function main (line 11) | func main() {
FILE: ch10/01_intro_to_wire/04_without_pset/wire.go
function initializeServer (line 20) | func initializeServer() (*rest.Server, error) {
FILE: ch10/01_intro_to_wire/04_without_pset/wire_gen.go
function initializeServer (line 22) | func initializeServer() (*rest.Server, error) {
FILE: ch10/02_advantages/01_dig/main.go
function main (line 19) | func main() {
function BuildContainer (line 36) | func BuildContainer() *dig.Container {
FILE: ch10/02_advantages/02_instantiation_order/handler.go
function NewGetPersonHandler (line 7) | func NewGetPersonHandler(model *GetPersonModel) *GetPersonHandler {
type GetPersonHandler (line 13) | type GetPersonHandler struct
method ServeHTTP (line 17) | func (g *GetPersonHandler) ServeHTTP(response http.ResponseWriter, req...
FILE: ch10/02_advantages/02_instantiation_order/injectors.go
function initializeDeps (line 11) | func initializeDeps() *GetPersonHandler {
FILE: ch10/02_advantages/02_instantiation_order/main.go
function main (line 3) | func main() {
FILE: ch10/02_advantages/02_instantiation_order/model.go
function NewGetPersonModel (line 8) | func NewGetPersonModel(db *sql.DB) *GetPersonModel {
type GetPersonModel (line 14) | type GetPersonModel struct
method LoadByID (line 18) | func (g *GetPersonModel) LoadByID(ID int) (*Person, error) {
type Person (line 22) | type Person struct
FILE: ch10/02_advantages/02_instantiation_order/providers.go
function ProvideHandler (line 9) | func ProvideHandler(model *GetPersonModel) *GetPersonHandler {
function ProvideModel (line 15) | func ProvideModel(db *sql.DB) *GetPersonModel {
function ProvideDatabase (line 21) | func ProvideDatabase() *sql.DB {
FILE: ch10/02_advantages/02_instantiation_order/wire_gen.go
function initializeDeps (line 10) | func initializeDeps() *GetPersonHandler {
FILE: ch10/03_applying/01_before_config/main.go
function main (line 18) | func main() {
FILE: ch10/03_applying/02_after_config/main.go
function main (line 17) | func main() {
FILE: ch10/03_applying/02_after_config/wire.go
function initializeConfig (line 15) | func initializeConfig() (*config.Config, error) {
FILE: ch10/03_applying/02_after_config/wire_gen.go
function initializeConfig (line 17) | func initializeConfig() (*config.Config, error) {
FILE: ch10/03_applying/03_after_exchange/main.go
function main (line 19) | func main() {
FILE: ch10/03_applying/03_after_exchange/wire.go
function initializeConfig (line 16) | func initializeConfig() (*config.Config, error) {
function initializeExchanger (line 21) | func initializeExchanger() (*exchange.Converter, error) {
FILE: ch10/03_applying/04_after_model/main.go
function main (line 19) | func main() {
FILE: ch10/03_applying/04_after_model/wire.go
function initializeConfig (line 18) | func initializeConfig() (*config.Config, error) {
function initializeGetter (line 23) | func initializeGetter() (*get.Getter, error) {
function initializeLister (line 28) | func initializeLister() (*list.Lister, error) {
function initializeRegisterer (line 33) | func initializeRegisterer() (*register.Registerer, error) {
FILE: ch10/03_applying/05_after_rest/main.go
function main (line 19) | func main() {
FILE: ch10/03_applying/05_after_rest/wire.go
function initializeServer (line 15) | func initializeServer() (*rest.Server, error) {
FILE: ch10/03_applying/05_after_rest/wire_gen.go
function initializeServer (line 22) | func initializeServer() (*rest.Server, error) {
FILE: ch10/03_applying/06_build_tag.go
function sayHello (line 9) | func sayHello() {
FILE: ch10/03_applying/06_build_tag_inverse.go
function sayHello (line 9) | func sayHello() {
FILE: ch10/03_applying/06_main.go
function main (line 3) | func main() {
FILE: ch10/04_disadvantages/01_complexity/main.go
constant configFile (line 11) | configFile = "config.json"
function main (line 14) | func main() {
type Config (line 57) | type Config struct
type Logger (line 61) | type Logger struct
method Debug (line 65) | func (l *Logger) Debug(msg string, args ...interface{}) {
method Warn (line 69) | func (l *Logger) Warn(msg string, args ...interface{}) {
method Error (line 73) | func (l *Logger) Error(msg string, args ...interface{}) {
type Server (line 77) | type Server struct
method Listen (line 81) | func (s *Server) Listen() {
FILE: ch10/acme/internal/config/config.go
constant DefaultEnvVar (line 13) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 16) | type Config struct
method Logger (line 37) | func (c *Config) Logger() logging.Logger {
method RegistrationBasePrice (line 46) | func (c *Config) RegistrationBasePrice() float64 {
method DataDSN (line 51) | func (c *Config) DataDSN() string {
method ExchangeBaseURL (line 56) | func (c *Config) ExchangeBaseURL() string {
method ExchangeAPIKey (line 61) | func (c *Config) ExchangeAPIKey() string {
method BindAddress (line 66) | func (c *Config) BindAddress() string {
function Load (line 71) | func Load() (*Config, error) {
function load (line 88) | func load(filename string) (*Config, error) {
FILE: ch10/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch10/acme/internal/logging/logging.go
type Logger (line 8) | type Logger interface
type LoggerStdOut (line 19) | type LoggerStdOut struct
method Debug (line 22) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 27) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 32) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 37) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch10/acme/internal/modules/data/dao.go
function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
type DAO (line 21) | type DAO struct
method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {
FILE: ch10/acme/internal/modules/data/data.go
constant defaultPersonID (line 13) | defaultPersonID = 0
constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 17) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 18) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 19) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Config (line 30) | type Config interface
type Person (line 52) | type Person struct
type scanner (line 66) | type scanner
function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch10/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
type testConfig (line 261) | type testConfig struct
method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
method DataDSN (line 269) | func (t *testConfig) DataDSN() string {
FILE: ch10/acme/internal/modules/data/tracker.go
type QueryTracker (line 10) | type QueryTracker interface
type noopTracker (line 16) | type noopTracker struct
method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
type LogTracker (line 31) | type LogTracker struct
method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {
FILE: ch10/acme/internal/modules/exchange/converter.go
constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 20) | defaultPrice = 0.0
function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
type Config (line 31) | type Config interface
type Converter (line 39) | type Converter struct
method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
method logger (line 140) | func (c *Converter) logger() logging.Logger {
type apiResponseFormat (line 145) | type apiResponseFormat struct
FILE: ch10/acme/internal/modules/exchange/converter_ext_bounday_test.go
function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {
FILE: ch10/acme/internal/modules/exchange/converter_int_bounday_test.go
function TestInternalBoundaryTest (line 13) | func TestInternalBoundaryTest(t *testing.T) {
type happyExchangeRateService (line 33) | type happyExchangeRateService struct
method ServeHTTP (line 36) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
type testConfig (line 52) | type testConfig struct
method Logger (line 58) | func (t *testConfig) Logger() logging.Logger {
method ExchangeBaseURL (line 63) | func (t *testConfig) ExchangeBaseURL() string {
method ExchangeAPIKey (line 68) | func (t *testConfig) ExchangeAPIKey() string {
FILE: ch10/acme/internal/modules/get/get.go
function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
type Config (line 24) | type Config interface
type Getter (line 31) | type Getter struct
method Do (line 37) | func (g *Getter) Do(ID int) (*data.Person, error) {
method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
type myLoader (line 60) | type myLoader interface
FILE: ch10/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {
FILE: ch10/acme/internal/modules/get/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...
FILE: ch10/acme/internal/modules/list/list.go
function NewLister (line 17) | func NewLister(cfg Config) *Lister {
type Config (line 24) | type Config interface
type Lister (line 31) | type Lister struct
method Do (line 37) | func (l *Lister) Do() ([]*data.Person, error) {
method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
type myLoader (line 78) | type myLoader interface
FILE: ch10/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {
FILE: ch10/acme/internal/modules/list/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...
FILE: ch10/acme/internal/modules/register/mock_my_saver_test.go
type mockMySaver (line 15) | type mockMySaver struct
method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...
FILE: ch10/acme/internal/modules/register/register.go
constant defaultPersonID (line 13) | defaultPersonID = 0
function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
type Exchanger (line 45) | type Exchanger interface
type Config (line 51) | type Config interface
type Registerer (line 63) | type Registerer struct
method Do (line 70) | func (r *Registerer) Do(ctx context.Context, in *data.Person) (int, er...
method validateInput (line 95) | func (r *Registerer) validateInput(in *data.Person) error {
method getPrice (line 115) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
method save (line 126) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
method getSaver (line 136) | func (r *Registerer) getSaver() mySaver {
method logger (line 144) | func (r *Registerer) logger() logging.Logger {
type mySaver (line 149) | type mySaver interface
FILE: ch10/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 16) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 48) | func TestRegisterer_Do_error(t *testing.T) {
type testConfig (line 79) | type testConfig struct
method Logger (line 82) | func (t *testConfig) Logger() logging.Logger {
method RegistrationBasePrice (line 87) | func (t *testConfig) RegistrationBasePrice() float64 {
method DataDSN (line 92) | func (t *testConfig) DataDSN() string {
type stubExchanger (line 96) | type stubExchanger struct
method Exchange (line 99) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...
FILE: ch10/acme/internal/rest/get.go
constant defaultPersonID (line 18) | defaultPersonID = 0
constant muxVarID (line 21) | muxVarID = "id"
type GetModel (line 26) | type GetModel interface
type GetConfig (line 31) | type GetConfig interface
function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
type GetHandler (line 47) | type GetHandler struct
method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *data.Person) ...
type getResponseFormat (line 117) | type getResponseFormat struct
FILE: ch10/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
type testConfig (line 146) | type testConfig struct
method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
method BindAddress (line 153) | func (*testConfig) BindAddress() string {
FILE: ch10/acme/internal/rest/list.go
type ListModel (line 13) | type ListModel interface
function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
type ListHandler (line 26) | type ListHandler struct
method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*data.Perso...
type listResponseFormat (line 66) | type listResponseFormat struct
type listResponseItemFormat (line 70) | type listResponseItemFormat struct
FILE: ch10/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch10/acme/internal/rest/mock_get_model_test.go
type MockGetModel (line 13) | type MockGetModel struct
method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*data.Person, error) {
FILE: ch10/acme/internal/rest/mock_list_model_test.go
type MockListModel (line 13) | type MockListModel struct
method Do (line 18) | func (_m *MockListModel) Do() ([]*data.Person, error) {
FILE: ch10/acme/internal/rest/mock_register_model_test.go
type MockRegisterModel (line 15) | type MockRegisterModel struct
method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *data.Person) ...
FILE: ch10/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch10/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch10/acme/internal/rest/register.go
type RegisterModel (line 15) | type RegisterModel interface
function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
type RegisterHandler (line 29) | type RegisterHandler struct
method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
type registerRequest (line 85) | type registerRequest struct
FILE: ch10/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {
FILE: ch10/acme/internal/rest/server.go
type Config (line 11) | type Config interface
function New (line 17) | func New(cfg Config,
type Server (line 32) | type Server struct
method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {
FILE: ch10/acme/main.go
function main (line 16) | func main() {
FILE: ch10/acme/main_test.go
function TestRegister (line 18) | func TestRegister(t *testing.T) {
function TestGet (line 46) | func TestGet(t *testing.T) {
function TestList (line 65) | func TestList(t *testing.T) {
function startTestServer (line 84) | func startTestServer(t *testing.T, ctx context.Context) string {
function getFreePort (line 110) | func getFreePort() (string, error) {
function getPort (line 139) | func getPort(addr fmt.Stringer) (string, error) {
FILE: ch10/acme/wire.go
function initializeServer (line 16) | func initializeServer() (*rest.Server, error) {
function initializeServerCustomConfig (line 21) | func initializeServerCustomConfig(_ exchange.Config, _ get.Config, _ lis...
FILE: ch10/acme/wire_gen.go
function initializeServer (line 19) | func initializeServer() (*rest.Server, error) {
function initializeServerCustomConfig (line 32) | func initializeServerCustomConfig(exchangeConfig exchange.Config, getCon...
FILE: ch10/fake.go
function init (line 3) | func init() {
FILE: ch11/01_di_induced_damage/01_long_param/01_long_param.go
function NewMyHandler (line 8) | func NewMyHandler(logger Logger, stats Instrumentation,
type MyHandler (line 19) | type MyHandler struct
method ServeHTTP (line 23) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
type Logger (line 28) | type Logger interface
type Instrumentation (line 36) | type Instrumentation interface
type Parser (line 42) | type Parser interface
type Formatter (line 47) | type Formatter interface
type RateLimiter (line 52) | type RateLimiter interface
type Datastore (line 58) | type Datastore interface
type Cache (line 63) | type Cache interface
FILE: ch11/01_di_induced_damage/02_long_param/01_long_param.go
function NewMyHandler (line 8) | func NewMyHandler(logger Logger, stats Instrumentation,
type MyHandler (line 19) | type MyHandler struct
method ServeHTTP (line 23) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
type Logger (line 28) | type Logger interface
type Instrumentation (line 36) | type Instrumentation interface
type Parser (line 42) | type Parser interface
type Formatter (line 47) | type Formatter interface
type RateLimiter (line 52) | type RateLimiter interface
type Loader (line 58) | type Loader interface
FILE: ch11/01_di_induced_damage/03_long_param/01_long_param.go
function NewMyHandler (line 8) | func NewMyHandler(config Config,
type MyHandler (line 23) | type MyHandler struct
method ServeHTTP (line 31) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
type Config (line 52) | type Config interface
type Logger (line 58) | type Logger interface
type Instrumentation (line 66) | type Instrumentation interface
type Parser (line 72) | type Parser interface
type Formatter (line 77) | type Formatter interface
type RateLimiter (line 82) | type RateLimiter interface
type Loader (line 88) | type Loader interface
FILE: ch11/01_di_induced_damage/04_long_param/01_long_param.go
function NewFancyFormatHandler (line 8) | func NewFancyFormatHandler(config Config,
type FancyFormatHandler (line 25) | type FancyFormatHandler struct
type MyHandler (line 30) | type MyHandler struct
method ServeHTTP (line 38) | func (m *MyHandler) ServeHTTP(response http.ResponseWriter, request *h...
type Config (line 59) | type Config interface
type Logger (line 65) | type Logger interface
type Instrumentation (line 73) | type Instrumentation interface
type Parser (line 79) | type Parser interface
type Formatter (line 84) | type Formatter interface
type FancyFormatter (line 89) | type FancyFormatter struct
method Format (line 91) | func (f *FancyFormatter) Format(response http.ResponseWriter, data []b...
type RateLimiter (line 98) | type RateLimiter interface
type Loader (line 104) | type Loader interface
FILE: ch11/01_di_induced_damage/04_long_param/01_long_param_test.go
function TestNewFancyFormatHandler (line 12) | func TestNewFancyFormatHandler(t *testing.T) {
type stubConfig (line 33) | type stubConfig struct
method Logger (line 35) | func (s *stubConfig) Logger() Logger {
method Instrumentation (line 39) | func (s *stubConfig) Instrumentation() Instrumentation {
type stubLogger (line 43) | type stubLogger struct
method Error (line 45) | func (s *stubLogger) Error(message string, args ...interface{}) {
method Warn (line 49) | func (s *stubLogger) Warn(message string, args ...interface{}) {
method Info (line 53) | func (s *stubLogger) Info(message string, args ...interface{}) {
method Debug (line 57) | func (s *stubLogger) Debug(message string, args ...interface{}) {
type stubInstrumentation (line 61) | type stubInstrumentation struct
method Count (line 63) | func (s *stubInstrumentation) Count(key string, value int) {
method Duration (line 67) | func (s *stubInstrumentation) Duration(key string, start time.Time) {
type stubParser (line 71) | type stubParser struct
method Extract (line 73) | func (s *stubParser) Extract(req *http.Request) (int, error) {
type stubRateLimiter (line 77) | type stubRateLimiter struct
method Acquire (line 79) | func (s *stubRateLimiter) Acquire() {
method Release (line 83) | func (s *stubRateLimiter) Release() {
type stubLoader (line 87) | type stubLoader struct
method Load (line 89) | func (s *stubLoader) Load(ID int) ([]byte, error) {
FILE: ch11/01_di_induced_damage/05_inject_sql/01_interface.go
type Connection (line 8) | type Connection interface
FILE: ch11/01_di_induced_damage/06_inject_sql/01_interface.go
type Database (line 7) | type Database interface
type Row (line 13) | type Row interface
type Rows (line 17) | type Rows interface
type Result (line 23) | type Result interface
FILE: ch11/01_di_induced_damage/06_inject_sql/02_implementation.go
type DatabaseImpl (line 8) | type DatabaseImpl struct
method QueryRowContext (line 12) | func (c *DatabaseImpl) QueryRowContext(ctx context.Context, query stri...
method QueryContext (line 16) | func (c *DatabaseImpl) QueryContext(ctx context.Context, query string,...
method ExecContext (line 20) | func (c *DatabaseImpl) ExecContext(ctx context.Context, query string, ...
type RowImpl (line 24) | type RowImpl struct
method Scan (line 28) | func (r *RowImpl) Scan(dest ...interface{}) error {
type RowsImpl (line 32) | type RowsImpl struct
method Scan (line 36) | func (r RowsImpl) Scan(dest ...interface{}) error {
method Close (line 40) | func (r RowsImpl) Close() error {
method Next (line 44) | func (r RowsImpl) Next() bool {
type ResultImpl (line 48) | type ResultImpl struct
method LastInsertId (line 52) | func (r *ResultImpl) LastInsertId() (int64, error) {
method RowsAffected (line 56) | func (r *ResultImpl) RowsAffected() (int64, error) {
FILE: ch11/01_di_induced_damage/06_inject_sql/02_implementation_test.go
function TestImplements (line 9) | func TestImplements(t *testing.T) {
FILE: ch11/01_di_induced_damage/06_inject_sql/dao.go
function NewDAO (line 13) | func NewDAO(cfg Config) *DAO {
type DAO (line 23) | type DAO struct
method Load (line 30) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
method LoadAll (line 61) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
method Save (line 104) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
FILE: ch11/01_di_induced_damage/06_inject_sql/data.go
constant defaultPersonID (line 12) | defaultPersonID = 0
constant sqlAllColumns (line 15) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 16) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 17) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 18) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Config (line 29) | type Config interface
type Person (line 48) | type Person struct
type scanner (line 62) | type scanner
function populatePerson (line 65) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch11/01_di_induced_damage/06_inject_sql/data_test.go
function TestSave_happyPath (line 15) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 51) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 88) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 120) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 188) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 255) | func convertSQLToRegex(in string) string {
type testConfig (line 259) | type testConfig struct
method DataDSN (line 262) | func (t *testConfig) DataDSN() string {
FILE: ch11/01_di_induced_damage/07_needless_indirection/example_test.go
function TestExample (line 12) | func TestExample(t *testing.T) {
FILE: ch11/01_di_induced_damage/08_needless_indirection/01_mux.go
type MyMux (line 8) | type MyMux interface
function buildRouter (line 15) | func buildRouter(mux MyMux) {
type getEndpoint (line 21) | type getEndpoint struct
method ServeHTTP (line 23) | func (*getEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
type listEndpoint (line 27) | type listEndpoint struct
method ServeHTTP (line 29) | func (*listEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
type saveEndpoint (line 33) | type saveEndpoint struct
method ServeHTTP (line 35) | func (*saveEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
FILE: ch11/01_di_induced_damage/08_needless_indirection/01_mux_test.go
function TestBuildRouter (line 9) | func TestBuildRouter(t *testing.T) {
FILE: ch11/01_di_induced_damage/08_needless_indirection/mock_my_mux_test.go
type MockMyMux (line 14) | type MockMyMux struct
method Handle (line 19) | func (_m *MockMyMux) Handle(pattern string, handler http.Handler) {
method Handler (line 24) | func (_m *MockMyMux) Handler(req *http.Request) (http.Handler, string) {
method ServeHTTP (line 47) | func (_m *MockMyMux) ServeHTTP(resp http.ResponseWriter, req *http.Req...
FILE: ch11/01_di_induced_damage/09_needless_indirection/01_mux.go
function buildRouter (line 8) | func buildRouter(mux *http.ServeMux) {
type getEndpoint (line 14) | type getEndpoint struct
method ServeHTTP (line 16) | func (*getEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
type listEndpoint (line 20) | type listEndpoint struct
method ServeHTTP (line 22) | func (*listEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
type saveEndpoint (line 26) | type saveEndpoint struct
method ServeHTTP (line 28) | func (*saveEndpoint) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
FILE: ch11/01_di_induced_damage/09_needless_indirection/01_mux_test.go
function TestBuildRouter (line 10) | func TestBuildRouter(t *testing.T) {
function extractHandler (line 22) | func extractHandler(router *http.ServeMux, path string) http.Handler {
FILE: ch11/01_di_induced_damage/10_needless_indirection/01_mux_e2e.go
function buildRouter (line 8) | func buildRouter(mux *http.ServeMux) {
type getEndpoint (line 14) | type getEndpoint struct
method ServeHTTP (line 16) | func (*getEndpoint) ServeHTTP(resp http.ResponseWriter, _ *http.Reques...
type listEndpoint (line 20) | type listEndpoint struct
method ServeHTTP (line 22) | func (*listEndpoint) ServeHTTP(resp http.ResponseWriter, _ *http.Reque...
type saveEndpoint (line 26) | type saveEndpoint struct
method ServeHTTP (line 28) | func (*saveEndpoint) ServeHTTP(resp http.ResponseWriter, _ *http.Reque...
FILE: ch11/01_di_induced_damage/10_needless_indirection/01_mux_e2e_test.go
function TestBuildRouter (line 12) | func TestBuildRouter(t *testing.T) {
function doGet (line 36) | func doGet(t *testing.T, address string) string {
FILE: ch11/01_di_induced_damage/11_service_locator/01_service_locator.go
function NewServiceLocator (line 3) | func NewServiceLocator() *ServiceLocator {
type ServiceLocator (line 9) | type ServiceLocator struct
method Store (line 14) | func (s *ServiceLocator) Store(key string, dep interface{}) {
method Get (line 19) | func (s *ServiceLocator) Get(key string) interface{} {
FILE: ch11/01_di_induced_damage/11_service_locator/02_usage.go
function Example (line 3) | func Example() {
function buildServiceLocator (line 8) | func buildServiceLocator() *ServiceLocator {
function useServiceLocator (line 19) | func useServiceLocator(locator *ServiceLocator) {
function useServiceLocatorExtended (line 27) | func useServiceLocatorExtended(locator *ServiceLocator) {
type Logger (line 42) | type Logger interface
type myLogger (line 46) | type myLogger struct
method Info (line 48) | func (m *myLogger) Info(message string, args ...interface{}) {
type Converter (line 52) | type Converter interface
type myConverter (line 56) | type myConverter struct
method Convert (line 58) | func (m *myConverter) Convert(in float64) (float64, error) {
FILE: ch11/02_premature_future/get.go
constant defaultPersonID (line 15) | defaultPersonID = 0
constant muxVarID (line 18) | muxVarID = "id"
type GetModel (line 22) | type GetModel interface
type GetConfig (line 27) | type GetConfig interface
type Formatter (line 32) | type Formatter interface
function NewGetHandler (line 37) | func NewGetHandler(cfg GetConfig, model GetModel, formatter Formatter) *...
type GetHandler (line 46) | type GetHandler struct
method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method buildOutput (line 103) | func (h *GetHandler) buildOutput(writer io.Writer, person *Person) err...
type getResponseFormat (line 124) | type getResponseFormat struct
type Person (line 132) | type Person struct
type Logger (line 140) | type Logger interface
FILE: ch11/03_mocking_http_requests/converter.go
constant urlFormat (line 15) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 18) | defaultPrice = 0.0
type Config (line 22) | type Config interface
function NewConverter (line 29) | func NewConverter(cfg Config, requester Requester) *Converter {
type Converter (line 37) | type Converter struct
method Exchange (line 43) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
method loadRateFromServer (line 61) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
method extractRate (line 84) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 107) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
method logger (line 125) | func (c *Converter) logger() Logger {
type apiResponseFormat (line 130) | type apiResponseFormat struct
type Requester (line 136) | type Requester interface
type Requesterer (line 141) | type Requesterer struct
method doRequest (line 144) | func (r *Requesterer) doRequest(ctx context.Context, url string) (*htt...
type Logger (line 161) | type Logger interface
type stubLogger (line 166) | type stubLogger struct
method Warn (line 168) | func (l *stubLogger) Warn(message string, args ...interface{}) {
method Error (line 172) | func (l *stubLogger) Error(message string, args ...interface{}) {
FILE: ch11/03_mocking_http_requests/converter_test.go
function TestExchange_invalidResponse (line 14) | func TestExchange_invalidResponse(t *testing.T) {
type testConfig (line 45) | type testConfig struct
method Logger (line 48) | func (t *testConfig) Logger() Logger {
method ExchangeBaseURL (line 52) | func (t *testConfig) ExchangeBaseURL() string {
method ExchangeAPIKey (line 56) | func (t *testConfig) ExchangeAPIKey() string {
FILE: ch11/03_mocking_http_requests/mock_requester_test.go
type mockRequester (line 15) | type mockRequester struct
method doRequest (line 20) | func (_m *mockRequester) doRequest(ctx context.Context, url string) (*...
FILE: ch11/acme/internal/config/config.go
constant DefaultEnvVar (line 13) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 16) | type Config struct
method Logger (line 37) | func (c *Config) Logger() logging.Logger {
method RegistrationBasePrice (line 46) | func (c *Config) RegistrationBasePrice() float64 {
method DataDSN (line 51) | func (c *Config) DataDSN() string {
method ExchangeBaseURL (line 56) | func (c *Config) ExchangeBaseURL() string {
method ExchangeAPIKey (line 61) | func (c *Config) ExchangeAPIKey() string {
method BindAddress (line 66) | func (c *Config) BindAddress() string {
function Load (line 71) | func Load() (*Config, error) {
function load (line 88) | func load(filename string) (*Config, error) {
FILE: ch11/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch11/acme/internal/logging/logging.go
type Logger (line 8) | type Logger interface
type LoggerStdOut (line 16) | type LoggerStdOut struct
method Debug (line 19) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 24) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 29) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 34) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch11/acme/internal/modules/data/dao.go
function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
type DAO (line 21) | type DAO struct
method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {
FILE: ch11/acme/internal/modules/data/data.go
constant defaultPersonID (line 13) | defaultPersonID = 0
constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 17) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 18) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 19) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Config (line 30) | type Config interface
type Person (line 52) | type Person struct
type scanner (line 66) | type scanner
function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch11/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
type testConfig (line 261) | type testConfig struct
method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
method DataDSN (line 269) | func (t *testConfig) DataDSN() string {
FILE: ch11/acme/internal/modules/data/tracker.go
type QueryTracker (line 10) | type QueryTracker interface
type noopTracker (line 16) | type noopTracker struct
method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
type LogTracker (line 31) | type LogTracker struct
method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {
FILE: ch11/acme/internal/modules/exchange/converter.go
constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 20) | defaultPrice = 0.0
function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
type Config (line 31) | type Config interface
type Converter (line 39) | type Converter struct
method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
method logger (line 140) | func (c *Converter) logger() logging.Logger {
type apiResponseFormat (line 145) | type apiResponseFormat struct
FILE: ch11/acme/internal/modules/exchange/converter_ext_bounday_test.go
function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {
FILE: ch11/acme/internal/modules/exchange/converter_int_bounday_test.go
function TestInternalBoundaryTest (line 14) | func TestInternalBoundaryTest(t *testing.T) {
type happyExchangeRateService (line 34) | type happyExchangeRateService struct
method ServeHTTP (line 37) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
function TestExchange_invalidResponseFromServer (line 52) | func TestExchange_invalidResponseFromServer(t *testing.T) {
type testConfig (line 81) | type testConfig struct
method Logger (line 87) | func (t *testConfig) Logger() logging.Logger {
method ExchangeBaseURL (line 92) | func (t *testConfig) ExchangeBaseURL() string {
method ExchangeAPIKey (line 97) | func (t *testConfig) ExchangeAPIKey() string {
FILE: ch11/acme/internal/modules/get/get.go
function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
type Config (line 24) | type Config interface
type Getter (line 31) | type Getter struct
method Do (line 37) | func (g *Getter) Do(ID int) (*Person, error) {
method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
method convert (line 59) | func (g *Getter) convert(in *data.Person) *Person {
type myLoader (line 70) | type myLoader interface
type Person (line 76) | type Person struct
FILE: ch11/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {
FILE: ch11/acme/internal/modules/get/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...
FILE: ch11/acme/internal/modules/list/list.go
function NewLister (line 17) | func NewLister(cfg Config) *Lister {
type Config (line 24) | type Config interface
type Lister (line 31) | type Lister struct
method Do (line 37) | func (l *Lister) Do() ([]*Person, error) {
method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
method convert (line 77) | func (l *Lister) convert(in []*data.Person) []*Person {
type myLoader (line 92) | type myLoader interface
type Person (line 98) | type Person struct
FILE: ch11/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {
FILE: ch11/acme/internal/modules/list/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...
FILE: ch11/acme/internal/modules/register/mock_exchanger_test.go
type MockExchanger (line 14) | type MockExchanger struct
method Exchange (line 19) | func (_m *MockExchanger) Exchange(ctx context.Context, basePrice float...
FILE: ch11/acme/internal/modules/register/mock_my_saver_test.go
type mockMySaver (line 15) | type mockMySaver struct
method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...
FILE: ch11/acme/internal/modules/register/register.go
constant defaultPersonID (line 13) | defaultPersonID = 0
function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
type Exchanger (line 46) | type Exchanger interface
type Config (line 52) | type Config interface
type Registerer (line 64) | type Registerer struct
method Do (line 71) | func (r *Registerer) Do(ctx context.Context, in *Person) (int, error) {
method validateInput (line 96) | func (r *Registerer) validateInput(in *Person) error {
method getPrice (line 116) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
method save (line 127) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
method getSaver (line 137) | func (r *Registerer) getSaver() mySaver {
method logger (line 145) | func (r *Registerer) logger() logging.Logger {
method convert (line 149) | func (r *Registerer) convert(in *Person) *data.Person {
type mySaver (line 160) | type mySaver interface
type Person (line 166) | type Person struct
FILE: ch11/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 15) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 47) | func TestRegisterer_Do_error(t *testing.T) {
function TestRegisterer_Do_exchangeError (line 77) | func TestRegisterer_Do_exchangeError(t *testing.T) {
type testConfig (line 113) | type testConfig struct
method Logger (line 116) | func (t *testConfig) Logger() logging.Logger {
method RegistrationBasePrice (line 121) | func (t *testConfig) RegistrationBasePrice() float64 {
method DataDSN (line 126) | func (t *testConfig) DataDSN() string {
type stubExchanger (line 130) | type stubExchanger struct
method Exchange (line 133) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...
FILE: ch11/acme/internal/rest/get.go
constant defaultPersonID (line 18) | defaultPersonID = 0
constant muxVarID (line 21) | muxVarID = "id"
type GetModel (line 26) | type GetModel interface
type GetConfig (line 31) | type GetConfig interface
function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
type GetHandler (line 47) | type GetHandler struct
method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *get.Person) e...
type getResponseFormat (line 117) | type getResponseFormat struct
FILE: ch11/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
type testConfig (line 146) | type testConfig struct
method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
method BindAddress (line 153) | func (*testConfig) BindAddress() string {
FILE: ch11/acme/internal/rest/list.go
type ListModel (line 13) | type ListModel interface
function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
type ListHandler (line 26) | type ListHandler struct
method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*list.Perso...
type listResponseFormat (line 66) | type listResponseFormat struct
type listResponseItemFormat (line 70) | type listResponseItemFormat struct
FILE: ch11/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch11/acme/internal/rest/mock_get_model_test.go
type MockGetModel (line 13) | type MockGetModel struct
method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*get.Person, error) {
FILE: ch11/acme/internal/rest/mock_list_model_test.go
type MockListModel (line 13) | type MockListModel struct
method Do (line 18) | func (_m *MockListModel) Do() ([]*list.Person, error) {
FILE: ch11/acme/internal/rest/mock_register_model_test.go
type MockRegisterModel (line 15) | type MockRegisterModel struct
method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *register.Pers...
FILE: ch11/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch11/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch11/acme/internal/rest/register.go
type RegisterModel (line 15) | type RegisterModel interface
function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
type RegisterHandler (line 29) | type RegisterHandler struct
method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
type registerRequest (line 85) | type registerRequest struct
FILE: ch11/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {
FILE: ch11/acme/internal/rest/server.go
type Config (line 11) | type Config interface
function New (line 17) | func New(cfg Config,
type Server (line 32) | type Server struct
method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {
FILE: ch11/acme/main.go
function main (line 16) | func main() {
FILE: ch11/acme/main_test.go
function TestRegister (line 18) | func TestRegister(t *testing.T) {
function TestGet (line 46) | func TestGet(t *testing.T) {
function TestList (line 65) | func TestList(t *testing.T) {
function startTestServer (line 84) | func startTestServer(t *testing.T, ctx context.Context) string {
function getFreePort (line 110) | func getFreePort() (string, error) {
function getPort (line 139) | func getPort(addr fmt.Stringer) (string, error) {
FILE: ch11/acme/wire.go
function initializeServer (line 16) | func initializeServer() (*rest.Server, error) {
function initializeServerCustomConfig (line 21) | func initializeServerCustomConfig(_ exchange.Config, _ get.Config, _ lis...
FILE: ch11/acme/wire_gen.go
function initializeServer (line 19) | func initializeServer() (*rest.Server, error) {
function initializeServerCustomConfig (line 32) | func initializeServerCustomConfig(exchangeConfig exchange.Config, getCon...
FILE: ch12/01_improvements/01_test_logging_test.go
function TestLogging (line 11) | func TestLogging(t *testing.T) {
type Calculator (line 27) | type Calculator struct
method divide (line 31) | func (c *Calculator) divide(dividend int, divisor int) int {
type Logger (line 41) | type Logger interface
type LogRecorder (line 46) | type LogRecorder struct
method Error (line 50) | func (l *LogRecorder) Error(message string, args ...interface{}) {
FILE: ch12/03_testing/01_mock_get_model.go
type MockGetModel (line 7) | type MockGetModel struct
method Do (line 11) | func (_m *MockGetModel) Do(ID int) (*Person, error) {
type Person (line 21) | type Person struct
FILE: ch12/04_new_service/01_data_with_cache/dao.go
type DAO (line 13) | type DAO struct
method Load (line 23) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
method loadFromCache (line 51) | func (d *DAO) loadFromCache(ID int) *Person {
method saveToCache (line 71) | func (d *DAO) saveToCache(ID int, person *Person) {
method buildCacheKey (line 84) | func (d *DAO) buildCacheKey(ID int) string {
FILE: ch12/04_new_service/01_data_with_cache/data.go
constant sqlAllColumns (line 12) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlLoadByID (line 13) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Config (line 22) | type Config interface
type Person (line 31) | type Person struct
type scanner (line 45) | type scanner
function populatePerson (line 48) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch12/04_new_service/01_data_with_cache/internal/cache/cache.go
type Cache (line 7) | type Cache struct
method Get (line 9) | func (c *Cache) Get(key string) ([]byte, error) {
method Set (line 13) | func (c *Cache) Set(key string, data []byte) error {
FILE: ch12/04_new_service/01_data_with_cache/internal/logging/logging.go
type Logger (line 8) | type Logger interface
type LoggerStdOut (line 16) | type LoggerStdOut struct
method Debug (line 19) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 24) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 29) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 34) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch12/acme/internal/config/config.go
constant DefaultEnvVar (line 13) | DefaultEnvVar = "ACME_CONFIG"
type Config (line 16) | type Config struct
method Logger (line 37) | func (c *Config) Logger() logging.Logger {
method RegistrationBasePrice (line 46) | func (c *Config) RegistrationBasePrice() float64 {
method DataDSN (line 51) | func (c *Config) DataDSN() string {
method ExchangeBaseURL (line 56) | func (c *Config) ExchangeBaseURL() string {
method ExchangeAPIKey (line 61) | func (c *Config) ExchangeAPIKey() string {
method BindAddress (line 66) | func (c *Config) BindAddress() string {
function Load (line 71) | func Load() (*Config, error) {
function load (line 88) | func load(filename string) (*Config, error) {
FILE: ch12/acme/internal/config/config_test.go
function TestLoad (line 10) | func TestLoad(t *testing.T) {
FILE: ch12/acme/internal/logging/logging.go
type Logger (line 8) | type Logger interface
type LoggerStdOut (line 16) | type LoggerStdOut struct
method Debug (line 19) | func (l LoggerStdOut) Debug(message string, args ...interface{}) {
method Info (line 24) | func (l LoggerStdOut) Info(message string, args ...interface{}) {
method Warn (line 29) | func (l LoggerStdOut) Warn(message string, args ...interface{}) {
method Error (line 34) | func (l LoggerStdOut) Error(message string, args ...interface{}) {
FILE: ch12/acme/internal/modules/data/dao.go
function NewDAO (line 11) | func NewDAO(cfg Config) *DAO {
type DAO (line 21) | type DAO struct
method Load (line 31) | func (d *DAO) Load(ctx context.Context, ID int) (*Person, error) {
method LoadAll (line 65) | func (d *DAO) LoadAll(ctx context.Context) ([]*Person, error) {
method Save (line 111) | func (d *DAO) Save(ctx context.Context, in *Person) (int, error) {
method getTracker (line 142) | func (d *DAO) getTracker() QueryTracker {
FILE: ch12/acme/internal/modules/data/data.go
constant defaultPersonID (line 13) | defaultPersonID = 0
constant sqlAllColumns (line 16) | sqlAllColumns = "id, fullname, phone, currency, price"
constant sqlInsert (line 17) | sqlInsert = "INSERT INTO person (fullname, phone, currency, price) V...
constant sqlLoadAll (line 18) | sqlLoadAll = "SELECT " + sqlAllColumns + " FROM person"
constant sqlLoadByID (line 19) | sqlLoadByID = "SELECT " + sqlAllColumns + " FROM person WHERE id = ? L...
type Config (line 30) | type Config interface
type Person (line 52) | type Person struct
type scanner (line 66) | type scanner
function populatePerson (line 69) | func populatePerson(scanner scanner) (*Person, error) {
FILE: ch12/acme/internal/modules/data/data_test.go
function TestSave_happyPath (line 17) | func TestSave_happyPath(t *testing.T) {
function TestSave_insertError (line 53) | func TestSave_insertError(t *testing.T) {
function TestSave_getDBError (line 90) | func TestSave_getDBError(t *testing.T) {
function TestLoadAll_tableDrivenTest (line 122) | func TestLoadAll_tableDrivenTest(t *testing.T) {
function TestLoad_tableDrivenTest (line 190) | func TestLoad_tableDrivenTest(t *testing.T) {
function convertSQLToRegex (line 257) | func convertSQLToRegex(in string) string {
type testConfig (line 261) | type testConfig struct
method Logger (line 264) | func (t *testConfig) Logger() logging.Logger {
method DataDSN (line 269) | func (t *testConfig) DataDSN() string {
FILE: ch12/acme/internal/modules/data/tracker.go
type QueryTracker (line 10) | type QueryTracker interface
type noopTracker (line 16) | type noopTracker struct
method Track (line 19) | func (_ *noopTracker) Track(_ string, _ time.Time) {
function NewLogTracker (line 24) | func NewLogTracker(logger logging.Logger) *LogTracker {
type LogTracker (line 31) | type LogTracker struct
method Track (line 36) | func (l *LogTracker) Track(key string, start time.Time) {
FILE: ch12/acme/internal/modules/exchange/converter.go
constant urlFormat (line 17) | urlFormat = "%s/api/historical?access_key=%s&date=2018-06-20¤cies=%s"
constant defaultPrice (line 20) | defaultPrice = 0.0
function NewConverter (line 24) | func NewConverter(cfg Config) *Converter {
type Config (line 31) | type Config interface
type Converter (line 39) | type Converter struct
method Exchange (line 44) | func (c *Converter) Exchange(ctx context.Context, basePrice float64, c...
method loadRateFromServer (line 62) | func (c *Converter) loadRateFromServer(ctx context.Context, currency s...
method extractRate (line 99) | func (c *Converter) extractRate(response *http.Response, currency stri...
method extractResponse (line 122) | func (c *Converter) extractResponse(response *http.Response) (*apiResp...
method logger (line 140) | func (c *Converter) logger() logging.Logger {
type apiResponseFormat (line 145) | type apiResponseFormat struct
FILE: ch12/acme/internal/modules/exchange/converter_ext_bounday_test.go
function TestExternalBoundaryTest (line 14) | func TestExternalBoundaryTest(t *testing.T) {
FILE: ch12/acme/internal/modules/exchange/converter_int_bounday_test.go
function TestInternalBoundaryTest (line 14) | func TestInternalBoundaryTest(t *testing.T) {
type happyExchangeRateService (line 34) | type happyExchangeRateService struct
method ServeHTTP (line 37) | func (*happyExchangeRateService) ServeHTTP(response http.ResponseWrite...
function TestExchange_invalidResponseFromServer (line 52) | func TestExchange_invalidResponseFromServer(t *testing.T) {
type testConfig (line 82) | type testConfig struct
method Logger (line 88) | func (t *testConfig) Logger() logging.Logger {
method ExchangeBaseURL (line 93) | func (t *testConfig) ExchangeBaseURL() string {
method ExchangeAPIKey (line 98) | func (t *testConfig) ExchangeAPIKey() string {
FILE: ch12/acme/internal/modules/get/get.go
function NewGetter (line 17) | func NewGetter(cfg Config) *Getter {
type Config (line 24) | type Config interface
type Getter (line 31) | type Getter struct
method Do (line 37) | func (g *Getter) Do(ID int) (*Person, error) {
method getLoader (line 51) | func (g *Getter) getLoader() myLoader {
method convert (line 59) | func (g *Getter) convert(in *data.Person) *Person {
type myLoader (line 70) | type myLoader interface
type Person (line 76) | type Person struct
FILE: ch12/acme/internal/modules/get/go_test.go
function TestGetter_Do_happyPath (line 13) | func TestGetter_Do_happyPath(t *testing.T) {
function TestGetter_Do_noSuchPerson (line 38) | func TestGetter_Do_noSuchPerson(t *testing.T) {
function TestGetter_Do_error (line 58) | func TestGetter_Do_error(t *testing.T) {
FILE: ch12/acme/internal/modules/get/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method Load (line 20) | func (_m *mockMyLoader) Load(ctx context.Context, ID int) (*data.Perso...
FILE: ch12/acme/internal/modules/list/list.go
function NewLister (line 17) | func NewLister(cfg Config) *Lister {
type Config (line 24) | type Config interface
type Lister (line 31) | type Lister struct
method Do (line 37) | func (l *Lister) Do() ([]*Person, error) {
method load (line 53) | func (l *Lister) load() ([]*data.Person, error) {
method getLoader (line 66) | func (l *Lister) getLoader() myLoader {
method convert (line 77) | func (l *Lister) convert(in []*data.Person) []*Person {
type myLoader (line 92) | type myLoader interface
type Person (line 98) | type Person struct
FILE: ch12/acme/internal/modules/list/list_test.go
function TestLister_Do_happyPath (line 13) | func TestLister_Do_happyPath(t *testing.T) {
function TestLister_Do_noResults (line 41) | func TestLister_Do_noResults(t *testing.T) {
function TestLister_Do_error (line 58) | func TestLister_Do_error(t *testing.T) {
FILE: ch12/acme/internal/modules/list/mock_my_loader_test.go
type mockMyLoader (line 15) | type mockMyLoader struct
method LoadAll (line 20) | func (_m *mockMyLoader) LoadAll(ctx context.Context) ([]*data.Person, ...
FILE: ch12/acme/internal/modules/register/mock_exchanger_test.go
type MockExchanger (line 14) | type MockExchanger struct
method Exchange (line 19) | func (_m *MockExchanger) Exchange(ctx context.Context, basePrice float...
FILE: ch12/acme/internal/modules/register/mock_my_saver_test.go
type mockMySaver (line 15) | type mockMySaver struct
method Save (line 20) | func (_m *mockMySaver) Save(ctx context.Context, in *data.Person) (int...
FILE: ch12/acme/internal/modules/register/register.go
constant defaultPersonID (line 13) | defaultPersonID = 0
function NewRegisterer (line 37) | func NewRegisterer(cfg Config, exchanger Exchanger) *Registerer {
type Exchanger (line 46) | type Exchanger interface
type Config (line 52) | type Config interface
type Registerer (line 64) | type Registerer struct
method Do (line 71) | func (r *Registerer) Do(ctx context.Context, in *Person) (int, error) {
method validateInput (line 96) | func (r *Registerer) validateInput(in *Person) error {
method getPrice (line 116) | func (r *Registerer) getPrice(ctx context.Context, currency string) (f...
method save (line 127) | func (r *Registerer) save(ctx context.Context, in *data.Person, price ...
method getSaver (line 137) | func (r *Registerer) getSaver() mySaver {
method logger (line 145) | func (r *Registerer) logger() logging.Logger {
method convert (line 149) | func (r *Registerer) convert(in *Person) *data.Person {
type mySaver (line 160) | type mySaver interface
type Person (line 166) | type Person struct
FILE: ch12/acme/internal/modules/register/register_test.go
function TestRegisterer_Do_happyPath (line 15) | func TestRegisterer_Do_happyPath(t *testing.T) {
function TestRegisterer_Do_error (line 47) | func TestRegisterer_Do_error(t *testing.T) {
function TestRegisterer_Do_exchangeError (line 77) | func TestRegisterer_Do_exchangeError(t *testing.T) {
type testConfig (line 113) | type testConfig struct
method Logger (line 116) | func (t *testConfig) Logger() logging.Logger {
method RegistrationBasePrice (line 121) | func (t *testConfig) RegistrationBasePrice() float64 {
method DataDSN (line 126) | func (t *testConfig) DataDSN() string {
type stubExchanger (line 130) | type stubExchanger struct
method Exchange (line 133) | func (s stubExchanger) Exchange(ctx context.Context, basePrice float64...
FILE: ch12/acme/internal/rest/get.go
constant defaultPersonID (line 18) | defaultPersonID = 0
constant muxVarID (line 21) | muxVarID = "id"
type GetModel (line 26) | type GetModel interface
type GetConfig (line 31) | type GetConfig interface
function NewGetHandler (line 36) | func NewGetHandler(cfg GetConfig, model GetModel) *GetHandler {
type GetHandler (line 47) | type GetHandler struct
method ServeHTTP (line 53) | func (h *GetHandler) ServeHTTP(response http.ResponseWriter, request *...
method extractID (line 79) | func (h *GetHandler) extractID(request *http.Request) (int, error) {
method writeJSON (line 103) | func (h *GetHandler) writeJSON(writer io.Writer, person *get.Person) e...
type getResponseFormat (line 117) | type getResponseFormat struct
FILE: ch12/acme/internal/rest/get_test.go
function TestGetHandler_ServeHTTP (line 18) | func TestGetHandler_ServeHTTP(t *testing.T) {
type testConfig (line 146) | type testConfig struct
method Logger (line 149) | func (t *testConfig) Logger() logging.Logger {
method BindAddress (line 153) | func (*testConfig) BindAddress() string {
FILE: ch12/acme/internal/rest/list.go
type ListModel (line 13) | type ListModel interface
function NewListHandler (line 18) | func NewListHandler(model ListModel) *ListHandler {
type ListHandler (line 26) | type ListHandler struct
method ServeHTTP (line 31) | func (h *ListHandler) ServeHTTP(response http.ResponseWriter, request ...
method writeJSON (line 49) | func (h *ListHandler) writeJSON(writer io.Writer, people []*list.Perso...
type listResponseFormat (line 66) | type listResponseFormat struct
type listResponseItemFormat (line 70) | type listResponseItemFormat struct
FILE: ch12/acme/internal/rest/list_test.go
function TestListHandler_ServeHTTP (line 16) | func TestListHandler_ServeHTTP(t *testing.T) {
FILE: ch12/acme/internal/rest/mock_get_model_test.go
type MockGetModel (line 13) | type MockGetModel struct
method Do (line 18) | func (_m *MockGetModel) Do(ID int) (*get.Person, error) {
FILE: ch12/acme/internal/rest/mock_list_model_test.go
type MockListModel (line 13) | type MockListModel struct
method Do (line 18) | func (_m *MockListModel) Do() ([]*list.Person, error) {
FILE: ch12/acme/internal/rest/mock_register_model_test.go
type MockRegisterModel (line 15) | type MockRegisterModel struct
method Do (line 20) | func (_m *MockRegisterModel) Do(ctx context.Context, in *register.Pers...
FILE: ch12/acme/internal/rest/not_found.go
function notFoundHandler (line 7) | func notFoundHandler(response http.ResponseWriter, _ *http.Request) {
FILE: ch12/acme/internal/rest/not_found_test.go
function TestNotFoundHandler_ServeHTTP (line 11) | func TestNotFoundHandler_ServeHTTP(t *testing.T) {
FILE: ch12/acme/internal/rest/register.go
type RegisterModel (line 15) | type RegisterModel interface
function NewRegisterHandler (line 20) | func NewRegisterHandler(model RegisterModel) *RegisterHandler {
type RegisterHandler (line 29) | type RegisterHandler struct
method ServeHTTP (line 34) | func (h *RegisterHandler) ServeHTTP(response http.ResponseWriter, requ...
method extractPayload (line 61) | func (h *RegisterHandler) extractPayload(request *http.Request) (*regi...
method register (line 74) | func (h *RegisterHandler) register(ctx context.Context, requestPayload...
type registerRequest (line 85) | type registerRequest struct
FILE: ch12/acme/internal/rest/register_test.go
function TestRegisterHandler_ServeHTTP (line 17) | func TestRegisterHandler_ServeHTTP(t *testing.T) {
function buildValidRegisterRequest (line 113) | func buildValidRegisterRequest() io.Reader {
FILE: ch12/acme/internal/rest/server.go
type Config (line 11) | type Config interface
function New (line 17) | func New(cfg Config,
type Server (line 32) | type Server struct
method Listen (line 43) | func (s *Server) Listen(stop <-chan struct{}) {
method buildRouter (line 65) | func (s *Server) buildRouter() http.Handler {
FILE: ch12/acme/main.go
function main (line 16) | func main() {
FILE: ch12/acme/main_test.go
function TestRegister (line 18) | func TestRegister(t *testing.T) {
function TestGet (line 46) | func TestGet(t *testing.T) {
function TestList (line 65) | func TestList(t *testing.T) {
function startTestServer (line 84) | func startTestServer(t *testing.T, ctx context.Context) string {
function getFreePort (line 110) | func getFreePort() (string, error) {
function getPort (line 139) | func getPort(addr fmt.Stringer) (string, error) {
FILE: ch12/acme/wire.go
function initializeServer (line 16) | func initializeServer() (*rest.Server, error) {
function initializeServerCustomConfig (line 21) | func initializeServerCustomConfig(_ exchange.Config, _ get.Config, _ lis...
FILE: ch12/acme/wire_gen.go
function initializeServer (line 19) | func initializeServer() (*rest.Server, error) {
function initializeServerCustomConfig (line 32) | func initializeServerCustomConfig(exchangeConfig exchange.Config, getCon...
FILE: ch12/fake.go
function init (line 3) | func init() {
FILE: fake.go
function init (line 3) | func init() {
FILE: resources/create.sql
type `acme` (line 3) | CREATE TABLE IF NOT EXISTS `acme`.`person` (
FILE: vendor/github.com/DATA-DOG/go-sqlmock/argument.go
type Argument (line 8) | type Argument interface
function AnyArg (line 16) | func AnyArg() Argument {
type anyArgument (line 20) | type anyArgument struct
method Match (line 22) | func (a anyArgument) Match(_ driver.Value) bool {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/driver.go
function init (line 12) | func init() {
type mockDriver (line 19) | type mockDriver struct
method Open (line 25) | func (d *mockDriver) Open(dsn string) (driver.Conn, error) {
function New (line 42) | func New() (*sql.DB, Sqlmock, error) {
function NewWithDSN (line 67) | func NewWithDSN(dsn string) (*sql.DB, Sqlmock, error) {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/expectations.go
type expectation (line 13) | type expectation interface
type commonExpectation (line 22) | type commonExpectation struct
method fulfilled (line 28) | func (e *commonExpectation) fulfilled() bool {
type ExpectedClose (line 34) | type ExpectedClose struct
method WillReturnError (line 39) | func (e *ExpectedClose) WillReturnError(err error) *ExpectedClose {
method String (line 45) | func (e *ExpectedClose) String() string {
type ExpectedBegin (line 55) | type ExpectedBegin struct
method WillReturnError (line 61) | func (e *ExpectedBegin) WillReturnError(err error) *ExpectedBegin {
method String (line 67) | func (e *ExpectedBegin) String() string {
method WillDelayFor (line 77) | func (e *ExpectedBegin) WillDelayFor(duration time.Duration) *Expected...
type ExpectedCommit (line 84) | type ExpectedCommit struct
method WillReturnError (line 89) | func (e *ExpectedCommit) WillReturnError(err error) *ExpectedCommit {
method String (line 95) | func (e *ExpectedCommit) String() string {
type ExpectedRollback (line 105) | type ExpectedRollback struct
method WillReturnError (line 110) | func (e *ExpectedRollback) WillReturnError(err error) *ExpectedRollback {
method String (line 116) | func (e *ExpectedRollback) String() string {
type ExpectedQuery (line 127) | type ExpectedQuery struct
method WithArgs (line 136) | func (e *ExpectedQuery) WithArgs(args ...driver.Value) *ExpectedQuery {
method WillReturnError (line 142) | func (e *ExpectedQuery) WillReturnError(err error) *ExpectedQuery {
method WillDelayFor (line 149) | func (e *ExpectedQuery) WillDelayFor(duration time.Duration) *Expected...
method String (line 155) | func (e *ExpectedQuery) String() string {
type ExpectedExec (line 182) | type ExpectedExec struct
method WithArgs (line 191) | func (e *ExpectedExec) WithArgs(args ...driver.Value) *ExpectedExec {
method WillReturnError (line 197) | func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec {
method WillDelayFor (line 204) | func (e *ExpectedExec) WillDelayFor(duration time.Duration) *ExpectedE...
method String (line 210) | func (e *ExpectedExec) String() string {
method WillReturnResult (line 246) | func (e *ExpectedExec) WillReturnResult(result driver.Result) *Expecte...
type ExpectedPrepare (line 253) | type ExpectedPrepare struct
method WillReturnError (line 265) | func (e *ExpectedPrepare) WillReturnError(err error) *ExpectedPrepare {
method WillReturnCloseError (line 271) | func (e *ExpectedPrepare) WillReturnCloseError(err error) *ExpectedPre...
method WillDelayFor (line 278) | func (e *ExpectedPrepare) WillDelayFor(duration time.Duration) *Expect...
method WillBeClosed (line 285) | func (e *ExpectedPrepare) WillBeClosed() *ExpectedPrepare {
method ExpectQuery (line 292) | func (e *ExpectedPrepare) ExpectQuery() *ExpectedQuery {
method ExpectExec (line 301) | func (e *ExpectedPrepare) ExpectExec() *ExpectedExec {
method String (line 309) | func (e *ExpectedPrepare) String() string {
type queryBasedExpectation (line 326) | type queryBasedExpectation struct
method attemptMatch (line 332) | func (e *queryBasedExpectation) attemptMatch(sql string, args []namedV...
method queryMatches (line 351) | func (e *queryBasedExpectation) queryMatches(sql string) bool {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/expectations_before_go18.go
method WillReturnRows (line 13) | func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery {
method argsMatches (line 18) | func (e *queryBasedExpectation) argsMatches(args []namedValue) error {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/expectations_go18.go
method WillReturnRows (line 14) | func (e *ExpectedQuery) WillReturnRows(rows ...*Rows) *ExpectedQuery {
method argsMatches (line 23) | func (e *queryBasedExpectation) argsMatches(args []namedValue) error {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/result.go
type result (line 10) | type result struct
method LastInsertId (line 33) | func (r *result) LastInsertId() (int64, error) {
method RowsAffected (line 37) | func (r *result) RowsAffected() (int64, error) {
function NewResult (line 18) | func NewResult(lastInsertID int64, rowsAffected int64) driver.Result {
function NewErrorResult (line 27) | func NewErrorResult(err error) driver.Result {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/rows.go
type rowSets (line 22) | type rowSets struct
method Columns (line 27) | func (rs *rowSets) Columns() []string {
method Close (line 31) | func (rs *rowSets) Close() error {
method Next (line 36) | func (rs *rowSets) Next(dest []driver.Value) error {
method String (line 51) | func (rs *rowSets) String() string {
method empty (line 72) | func (rs *rowSets) empty() bool {
type Rows (line 83) | type Rows struct
method CloseError (line 105) | func (r *Rows) CloseError(err error) *Rows {
method RowError (line 113) | func (r *Rows) RowError(row int, err error) *Rows {
method AddRow (line 122) | func (r *Rows) AddRow(values ...driver.Value) *Rows {
method FromCSVString (line 140) | func (r *Rows) FromCSVString(s string) *Rows {
function NewRows (line 94) | func NewRows(columns []string) *Rows {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/rows_go18.go
method HasNextResultSet (line 8) | func (rs *rowSets) HasNextResultSet() bool {
method NextResultSet (line 13) | func (rs *rowSets) NextResultSet() error {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/sqlmock.go
type Sqlmock (line 24) | type Sqlmock interface
type sqlmock (line 78) | type sqlmock struct
method open (line 87) | func (c *sqlmock) open() (*sql.DB, Sqlmock, error) {
method ExpectClose (line 95) | func (c *sqlmock) ExpectClose() *ExpectedClose {
method MatchExpectationsInOrder (line 101) | func (c *sqlmock) MatchExpectationsInOrder(b bool) {
method Close (line 109) | func (c *sqlmock) Close() error {
method ExpectationsWereMet (line 152) | func (c *sqlmock) ExpectationsWereMet() error {
method Begin (line 169) | func (c *sqlmock) Begin() (driver.Tx, error) {
method begin (line 181) | func (c *sqlmock) begin() (*ExpectedBegin, error) {
method ExpectBegin (line 216) | func (c *sqlmock) ExpectBegin() *ExpectedBegin {
method Exec (line 223) | func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Resu...
method exec (line 243) | func (c *sqlmock) exec(query string, args []namedValue) (*ExpectedExec...
method ExpectExec (line 300) | func (c *sqlmock) ExpectExec(sqlRegexStr string) *ExpectedExec {
method Prepare (line 309) | func (c *sqlmock) Prepare(query string) (driver.Stmt, error) {
method prepare (line 321) | func (c *sqlmock) prepare(query string) (*ExpectedPrepare, error) {
method ExpectPrepare (line 370) | func (c *sqlmock) ExpectPrepare(sqlRegexStr string) *ExpectedPrepare {
method Query (line 384) | func (c *sqlmock) Query(query string, args []driver.Value) (driver.Row...
method query (line 404) | func (c *sqlmock) query(query string, args []namedValue) (*ExpectedQue...
method ExpectQuery (line 462) | func (c *sqlmock) ExpectQuery(sqlRegexStr string) *ExpectedQuery {
method ExpectCommit (line 470) | func (c *sqlmock) ExpectCommit() *ExpectedCommit {
method ExpectRollback (line 476) | func (c *sqlmock) ExpectRollback() *ExpectedRollback {
method Commit (line 483) | func (c *sqlmock) Commit() error {
method Rollback (line 518) | func (c *sqlmock) Rollback() error {
type namedValue (line 377) | type namedValue struct
FILE: vendor/github.com/DATA-DOG/go-sqlmock/sqlmock_go18.go
method QueryContext (line 15) | func (c *sqlmock) QueryContext(ctx context.Context, query string, args [...
method ExecContext (line 38) | func (c *sqlmock) ExecContext(ctx context.Context, query string, args []...
method BeginTx (line 61) | func (c *sqlmock) BeginTx(ctx context.Context, opts driver.TxOptions) (d...
method PrepareContext (line 79) | func (c *sqlmock) PrepareContext(ctx context.Context, query string) (dri...
method Ping (line 99) | func (c *sqlmock) Ping(ctx context.Context) error {
method ExecContext (line 104) | func (stmt *statement) ExecContext(ctx context.Context, args []driver.Na...
method QueryContext (line 109) | func (stmt *statement) QueryContext(ctx context.Context, args []driver.N...
FILE: vendor/github.com/DATA-DOG/go-sqlmock/statement.go
type statement (line 7) | type statement struct
method Close (line 13) | func (stmt *statement) Close() error {
method NumInput (line 18) | func (stmt *statement) NumInput() int {
method Exec (line 22) | func (stmt *statement) Exec(args []driver.Value) (driver.Result, error) {
method Query (line 26) | func (stmt *statement) Query(args []driver.Value) (driver.Rows, error) {
FILE: vendor/github.com/DATA-DOG/go-sqlmock/util.go
function stripQuery (line 11) | func stripQuery(q string) (s string) {
FILE: vendor/github.com/davecgh/go-spew/spew/bypass.go
constant UnsafeDisabled (line 31) | UnsafeDisabled = false
constant ptrSize (line 34) | ptrSize = unsafe.Sizeof((*byte)(nil))
function init (line 66) | func init() {
function unsafeReflectValue (line 122) | func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
FILE: vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
constant UnsafeDisabled (line 28) | UnsafeDisabled = true
function unsafeReflectValue (line 36) | func unsafeReflectValue(v reflect.Value) reflect.Value {
FILE: vendor/github.com/davecgh/go-spew/spew/common.go
function catchPanic (line 72) | func catchPanic(w io.Writer, v reflect.Value) {
function handleMethods (line 85) | func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handl...
function printBool (line 144) | func printBool(w io.Writer, val bool) {
function printInt (line 153) | func printInt(w io.Writer, val int64, base int) {
function printUint (line 158) | func printUint(w io.Writer, val uint64, base int) {
function printFloat (line 164) | func printFloat(w io.Writer, val float64, precision int) {
function printComplex (line 170) | func printComplex(w io.Writer, c complex128, floatPrecision int) {
function printHexPtr (line 185) | func printHexPtr(w io.Writer, p uintptr) {
type valuesSorter (line 219) | type valuesSorter struct
method Len (line 279) | func (s *valuesSorter) Len() int {
method Swap (line 285) | func (s *valuesSorter) Swap(i, j int) {
method Less (line 326) | func (s *valuesSorter) Less(i, j int) bool {
function newValuesSorter (line 228) | func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Inter...
function canSortSimply (line 256) | func canSortSimply(kind reflect.Kind) bool {
function valueSortLess (line 295) | func valueSortLess(a, b reflect.Value) bool {
function sortValues (line 336) | func sortValues(values []reflect.Value, cs *ConfigState) {
FILE: vendor/github.com/davecgh/go-spew/spew/config.go
type ConfigState (line 37) | type ConfigState struct
method Errorf (line 115) | func (c *ConfigState) Errorf(format string, a ...interface{}) (err err...
method Fprint (line 127) | func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, er...
method Fprintf (line 139) | func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interfa...
method Fprintln (line 150) | func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, ...
method Print (line 162) | func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
method Printf (line 174) | func (c *ConfigState) Printf(format string, a ...interface{}) (n int, ...
method Println (line 186) | func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
method Sprint (line 197) | func (c *ConfigState) Sprint(a ...interface{}) string {
method Sprintf (line 208) | func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
method Sprintln (line 219) | func (c *ConfigState) Sprintln(a ...interface{}) string {
method NewFormatter (line 240) | func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
method Fdump (line 246) | func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
method Dump (line 273) | func (c *ConfigState) Dump(a ...interface{}) {
method Sdump (line 279) | func (c *ConfigState) Sdump(a ...interface{}) string {
method convertArgs (line 288) | func (c *ConfigState) convertArgs(args []interface{}) (formatters []in...
function NewDefaultConfig (line 304) | func NewDefaultConfig() *ConfigState {
FILE: vendor/github.com/davecgh/go-spew/spew/dump.go
type dumpState (line 51) | type dumpState struct
method indent (line 62) | func (d *dumpState) indent() {
method unpackValue (line 73) | func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
method dumpPtr (line 81) | func (d *dumpState) dumpPtr(v reflect.Value) {
method dumpSlice (line 161) | func (d *dumpState) dumpSlice(v reflect.Value) {
method dump (line 251) | func (d *dumpState) dump(v reflect.Value) {
function fdump (line 453) | func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
function Fdump (line 472) | func Fdump(w io.Writer, a ...interface{}) {
function Sdump (line 478) | func Sdump(a ...interface{}) string {
function Dump (line 507) | func Dump(a ...interface{}) {
FILE: vendor/github.com/davecgh/go-spew/spew/format.go
constant supportedFlags (line 28) | supportedFlags = "0-+# "
type formatState (line 34) | type formatState struct
method buildDefaultFormat (line 47) | func (f *formatState) buildDefaultFormat() (format string) {
method constructOrigFormat (line 65) | func (f *formatState) constructOrigFormat(verb rune) (format string) {
method unpackValue (line 94) | func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
method formatPtr (line 105) | func (f *formatState) formatPtr(v reflect.Value) {
method format (line 201) | func (f *formatState) format(v reflect.Value) {
method Format (line 371) | func (f *formatState) Format(fs fmt.State, verb rune) {
function newFormatter (line 394) | func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
function NewFormatter (line 417) | func NewFormatter(v interface{}) fmt.Formatter {
FILE: vendor/github.com/davecgh/go-spew/spew/spew.go
function Errorf (line 32) | func Errorf(format string, a ...interface{}) (err error) {
function Fprint (line 44) | func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
function Fprintf (line 56) | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err e...
function Fprintln (line 67) | func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
function Print (line 79) | func Print(a ...interface{}) (n int, err error) {
function Printf (line 91) | func Printf(format string, a ...interface{}) (n int, err error) {
function Println (line 103) | func Println(a ...interface{}) (n int, err error) {
function Sprint (line 114) | func Sprint(a ...interface{}) string {
function Sprintf (line 125) | func Sprintf(format string, a ...interface{}) string {
function Sprintln (line 136) | func Sprintln(a ...interface{}) string {
function convertArgs (line 142) | func convertArgs(args []interface{}) (formatters []interface{}) {
FILE: vendor/github.com/go-sql-driver/mysql/appengine.go
function init (line 17) | func init() {
FILE: vendor/github.com/go-sql-driver/mysql/buffer.go
constant defaultBufSize (line 17) | defaultBufSize = 4096
type buffer (line 24) | type buffer struct
method fill (line 41) | func (b *buffer) fill(need int) error {
method readNext (line 94) | func (b *buffer) readNext(need int) ([]byte, error) {
method takeBuffer (line 112) | func (b *buffer) takeBuffer(length int) []byte {
method takeSmallBuffer (line 132) | func (b *buffer) takeSmallBuffer(length int) []byte {
method takeCompleteBuffer (line 142) | func (b *buffer) takeCompleteBuffer() []byte {
function newBuffer (line 32) | func newBuffer(nc net.Conn) buffer {
FILE: vendor/github.com/go-sql-driver/mysql/collations.go
constant defaultCollation (line 11) | defaultCollation = "utf8_general_ci"
constant binaryCollation (line 12) | binaryCollation = "binary"
FILE: vendor/github.com/go-sql-driver/mysql/connection.go
type mysqlContext (line 21) | type mysqlContext interface
type mysqlConn (line 30) | type mysqlConn struct
method handleParams (line 54) | func (mc *mysqlConn) handleParams() (err error) {
method markBadConn (line 83) | func (mc *mysqlConn) markBadConn(err error) error {
method Begin (line 93) | func (mc *mysqlConn) Begin() (driver.Tx, error) {
method begin (line 97) | func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
method Close (line 115) | func (mc *mysqlConn) Close() (err error) {
method cleanup (line 130) | func (mc *mysqlConn) cleanup() {
method error (line 145) | func (mc *mysqlConn) error() error {
method Prepare (line 155) | func (mc *mysqlConn) Prepare(query string) (driver.Stmt, err
Condensed preview — 656 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,775K chars).
[
{
"path": ".gitignore",
"chars": 268,
"preview": "# Created by .ignore support plugin (hsz.mobi)\n### Go template\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*."
},
{
"path": "LICENSE",
"chars": 1062,
"preview": "MIT License\n\nCopyright (c) 2018 Packt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
},
{
"path": "README.md",
"chars": 8170,
"preview": "\n\n\n# Hands-On Dependency Injection in Go\r\n\r\n<a href=\"https://www.packtpub.com/application-development/hands-dependency-i"
},
{
"path": "ch01/01_defining_depenency_injection/01_interface.go",
"chars": 904,
"preview": "package defining_depenency_injection\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n)\n\n// Saver persists the supplied bytes\ntype S"
},
{
"path": "ch01/01_defining_depenency_injection/02_function_literal.go",
"chars": 676,
"preview": "package defining_depenency_injection\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// LoadPerson will load the requested person by ID.\n/"
},
{
"path": "ch01/01_defining_depenency_injection/03_test_without_nfs_test.go",
"chars": 705,
"preview": "package defining_depenency_injection\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/t"
},
{
"path": "ch01/01_defining_depenency_injection/04_fail_test_without_nfs_test.go",
"chars": 538,
"preview": "package defining_depenency_injection\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/"
},
{
"path": "ch01/02_code_smells/01_code_bloat/01_switch_type.go",
"chars": 510,
"preview": "package code_bloat\n\nimport (\n\t\"strconv\"\n)\n\nfunc AppendValue(buffer []byte, in interface{}) []byte {\n\tvar value []byte\n\n\t"
},
{
"path": "ch01/02_code_smells/02_resistance_to_change/01_shotgun_surgey.go",
"chars": 585,
"preview": "package _2_resistance_to_change\n\nimport (\n\t\"database/sql\"\n\t\"io\"\n)\n\n// Renderer will render a person to the supplied writ"
},
{
"path": "ch01/02_code_smells/03_wasted_effort/01_excessive_comments.go",
"chars": 773,
"preview": "package wasted_effort\n\n// Excessive comments\nfunc outputOrderedPeopleA(in []*Person) {\n\t// This code orders people by na"
},
{
"path": "ch01/02_code_smells/03_wasted_effort/02_complicated_go.go",
"chars": 271,
"preview": "package wasted_effort\n\nimport (\n\t\"image\"\n\t\"image/color\"\n\t\"math\"\n)\n\nfunc d(r, v float64, i *image.RGBA, c color.Color) {\n"
},
{
"path": "ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/config/config.go",
"chars": 633,
"preview": "// +build bad\n\npackage config\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch01"
},
{
"path": "ch01/02_code_smells/04_tight_coupling/01_circular_dependencies/payment/currency.go",
"chars": 497,
"preview": "// +build bad\n\npackage payment\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch0"
},
{
"path": "ch01/02_code_smells/04_tight_coupling/02_object_orgy.go",
"chars": 1070,
"preview": "package _4_tight_coupling\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\ntype PageLoader struct {\n}\n\nfunc (o *PageLoade"
},
{
"path": "ch01/02_code_smells/04_tight_coupling/03_feature_envy.go",
"chars": 1336,
"preview": "package _4_tight_coupling\n\nimport (\n\t\"errors\"\n\t\"time\"\n)\n\ntype searchRequest struct {\n\tquery string\n\tstart time.Time\n\tend"
},
{
"path": "ch02/01_single_responsibility_principle/01_responsibility_vs_change.go",
"chars": 606,
"preview": "package srp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// CalculatorV1 calculates the test coverage for a directory and it's sub-director"
},
{
"path": "ch02/01_single_responsibility_principle/02_responsibility_vs_change.go",
"chars": 813,
"preview": "package srp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// CalculatorV2 calculates the test coverage for a directory and it's sub-director"
},
{
"path": "ch02/01_single_responsibility_principle/03_responsibility_vs_change.go",
"chars": 1011,
"preview": "package srp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n)\n\n// CalculatorV3 calculates the test coverage for a directory and it's sub-director"
},
{
"path": "ch02/01_single_responsibility_principle/04_long_method.go",
"chars": 853,
"preview": "package srp\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc loadUserHandlerLong(resp http.Resp"
},
{
"path": "ch02/01_single_responsibility_principle/04_long_method_test.go",
"chars": 872,
"preview": "package srp\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\""
},
{
"path": "ch02/01_single_responsibility_principle/05_srp_method.go",
"chars": 1059,
"preview": "package srp\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc loadUserHandlerSRP(resp http.ResponseWriter, req *"
},
{
"path": "ch02/02_open_closed_principle/01_open_closed_failure.go",
"chars": 848,
"preview": "package ocp\n\nimport (\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc BuildOutputOCPFail(response http.ResponseWriter, format string, person P"
},
{
"path": "ch02/02_open_closed_principle/02_open_closed_success.go",
"chars": 883,
"preview": "package ocp\n\nimport (\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc BuildOutputOCPSuccess(response http.ResponseWriter, formatter PersonForm"
},
{
"path": "ch02/02_open_closed_principle/03_shotgun_surgery.go",
"chars": 1019,
"preview": "package ocp\n\nimport (\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc GetUserHandlerV1(resp http.ResponseWriter, req *http.Request) {\n\t//"
},
{
"path": "ch02/02_open_closed_principle/04_after_shotgun_surgery.go",
"chars": 1023,
"preview": "package ocp\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n)\n\nfunc GetUserHandlerV2(resp http.ResponseWriter, req"
},
{
"path": "ch02/02_open_closed_principle/05_composition.go",
"chars": 1150,
"preview": "package ocp\n\nimport (\n\t\"database/sql\"\n)\n\ntype rowConverter struct {\n}\n\n// populate the supplied Person from *sql.Row or "
},
{
"path": "ch02/02_open_closed_principle/06_handler_struct.go",
"chars": 314,
"preview": "package ocp\n\nimport (\n\t\"net/http\"\n)\n\n// a HTTP health check handler in long form\ntype healthCheckLong struct {\n}\n\nfunc ("
},
{
"path": "ch02/02_open_closed_principle/07_handler_func.go",
"chars": 285,
"preview": "package ocp\n\nimport (\n\t\"net/http\"\n)\n\n// a HTTP health check handler in short form\nfunc healthCheckShort(resp http.Respon"
},
{
"path": "ch02/03_liskov_substitution_principle/01_violation/example.go",
"chars": 653,
"preview": "package lsp_violation\n\nfunc Go(vehicle actions) {\n\tif sled, ok := vehicle.(*Sled); ok {\n\t\tsled.pushStart()\n\t} else {\n\t\tv"
},
{
"path": "ch02/03_liskov_substitution_principle/02_fixed/example.go",
"chars": 670,
"preview": "package fixedv1\n\nfunc Go(vehicle actions) {\n\tswitch concrete := vehicle.(type) {\n\tcase poweredActions:\n\t\tconcrete.startE"
},
{
"path": "ch02/03_liskov_substitution_principle/03_fixed/example.go",
"chars": 482,
"preview": "package fixedv2\n\nfunc Go(vehicle actions) {\n\tvehicle.start()\n\tvehicle.drive()\n}\n\ntype actions interface {\n\tstart()\n\tdriv"
},
{
"path": "ch02/03_liskov_substitution_principle/04_behaviour.go",
"chars": 452,
"preview": "package lsp\n\ntype Collection interface {\n\tAdd(item interface{})\n\tGet(index int) interface{}\n}\n\ntype CollectionImpl struc"
},
{
"path": "ch02/03_liskov_substitution_principle/05_behaviour_fixed.go",
"chars": 452,
"preview": "package lsp\n\ntype ImmutableCollection interface {\n\tGet(index int) interface{}\n}\n\ntype MutableCollection interface {\n\tImm"
},
{
"path": "ch02/04_interface_segregation_principle/01_fat_interface.go",
"chars": 1165,
"preview": "package isp\n\nimport (\n\t\"context\"\n)\n\ntype Item struct {\n\tKey string\n\tPayload []byte\n}\n\ntype FatDbInterface interface "
},
{
"path": "ch02/04_interface_segregation_principle/02_thin_interface.go",
"chars": 406,
"preview": "package isp\n\ntype myDB interface {\n\tGetItem(ID int) (Item, error)\n\tPutItem(item Item) error\n}\n\ntype CacheV2 struct {\n\tdb"
},
{
"path": "ch02/04_interface_segregation_principle/03_repeated_inputs.go",
"chars": 847,
"preview": "package isp\n\nimport (\n\t\"context\"\n\t\"errors\"\n)\n\nfunc Encrypt(ctx context.Context, data []byte) ([]byte, error) {\n\t// As th"
},
{
"path": "ch02/04_interface_segregation_principle/04_repeated_inputs.go",
"chars": 879,
"preview": "package isp\n\nimport (\n\t\"errors\"\n)\n\ntype Value interface {\n\tValue(key interface{}) interface{}\n}\n\ntype Monitor interface "
},
{
"path": "ch02/04_interface_segregation_principle/05_repeated_inputs.go",
"chars": 303,
"preview": "package isp\n\nimport (\n\t\"context\"\n)\n\nfunc UseEncryptV2() {\n\t// create a context\n\tctx, cancel := context.WithCancel(contex"
},
{
"path": "ch02/04_interface_segregation_principle/06_implicit_interfaces.go",
"chars": 289,
"preview": "package isp\n\nimport (\n\t\"fmt\"\n)\n\ntype Talker interface {\n\tSayHello() string\n}\n\ntype Dog struct{}\n\n// The method implicitl"
},
{
"path": "ch03/01_optimizing_for_humans/01_not_so_simple.go",
"chars": 430,
"preview": "package humans\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc NotSoSimple(ID int64, name string, age int, registered b"
},
{
"path": "ch03/01_optimizing_for_humans/02_start_simple.go",
"chars": 250,
"preview": "package humans\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nfunc Simpler(ID int64, name string, age int, registered bool) string {\n\tna"
},
{
"path": "ch03/01_optimizing_for_humans/03_too_abstract.go",
"chars": 317,
"preview": "package humans\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\ntype myGetter interface {\n\tGet(url string) (*http.Response, error)\n"
},
{
"path": "ch03/01_optimizing_for_humans/04_common_concept.go",
"chars": 230,
"preview": "package humans\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\nfunc CommonConcept(url string) ([]byte, error) {\n\tresp, err := http"
},
{
"path": "ch03/01_optimizing_for_humans/05_boolean_param.go",
"chars": 254,
"preview": "package humans\n\nimport (\n\t\"time\"\n)\n\ntype Pet struct {\n\tName string\n\tDog bool\n\tBorn time.Time\n}\n\nfunc NewPet(name string"
},
{
"path": "ch03/01_optimizing_for_humans/06_hidden_boolean.go",
"chars": 220,
"preview": "package humans\n\nconst (\n\tisDog = true\n\tisCat = false\n)\n\nfunc NewDog(name string) Pet {\n\treturn NewPet(name, isDog)\n}\n\nfu"
},
{
"path": "ch03/01_optimizing_for_humans/07_wide_formatter.go",
"chars": 155,
"preview": "package humans\n\ntype WideFormatter interface {\n\tToCSV(pets []Pet) ([]byte, error)\n\tToGOB(pets []Pet) ([]byte, error)\n\tTo"
},
{
"path": "ch03/01_optimizing_for_humans/08_thin_formatters.go",
"chars": 508,
"preview": "package humans\n\ntype ThinFormatter interface {\n\tFormat(pets []Pet) ([]byte, error)\n}\n\ntype CSVFormatter struct{}\n\nfunc ("
},
{
"path": "ch03/01_optimizing_for_humans/09_extra_config.go",
"chars": 427,
"preview": "package humans\n\n// PetFetcher searches the data store for pets whos name matches the search string.\n// Limit is optional"
},
{
"path": "ch03/02_unit_tests/01_loader.go",
"chars": 1417,
"preview": "package unit_tests\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype L"
},
{
"path": "ch03/02_unit_tests/02_language_feature.go",
"chars": 290,
"preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype Pet struct {\n\tName string\n}\n\nfunc"
},
{
"path": "ch03/02_unit_tests/03_simple_test.go",
"chars": 268,
"preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc concat(a, b string) string {\n\tret"
},
{
"path": "ch03/02_unit_tests/04_test_from_api.go",
"chars": 627,
"preview": "package unit_tests\n\nimport (\n\t\"database/sql\"\n)\n\ntype PetSaver struct{}\n\n// save the supplied pet and return the ID\nfunc "
},
{
"path": "ch03/02_unit_tests/05_repeated_code.go",
"chars": 570,
"preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Round the supplied number to the ne"
},
{
"path": "ch03/02_unit_tests/06_tdt.go",
"chars": 536,
"preview": "package unit_tests\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestRound(t *testing.T) {\n\tscenar"
},
{
"path": "ch03/02_unit_tests/07_person_loader.go",
"chars": 432,
"preview": "package unit_tests\n\nimport (\n\t\"errors\"\n)\n\nvar ErrNotFound = errors.New(\"person not found\")\n\ntype Person struct {\n\tName s"
},
{
"path": "ch03/02_unit_tests/08_stub.go",
"chars": 213,
"preview": "package unit_tests\n\n// Stubbed implementation of PersonLoader\ntype PersonLoaderStub struct {\n\tPerson *Person\n\tError err"
},
{
"path": "ch03/02_unit_tests/09_stub_tdt.go",
"chars": 1048,
"preview": "package unit_tests\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestLoadPersonNameStubs"
},
{
"path": "ch03/02_unit_tests/10_mocks.go",
"chars": 1773,
"preview": "package unit_tests\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/m"
},
{
"path": "ch03/03_test_induced_damage/01_io_closer.go",
"chars": 234,
"preview": "package test_damage\n\nimport (\n\t\"io\"\n)\n\nfunc WriteAndClose(destination io.WriteCloser, contents string) error {\n\tdefer de"
},
{
"path": "ch03/03_test_induced_damage/02_json.go",
"chars": 262,
"preview": "package test_damage\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\nfunc PrintAsJSON(destination io.Writer, plant Plant) error {\n\tby"
},
{
"path": "ch03/04_visualizing_dependencies/depgraph.sh",
"chars": 771,
"preview": "#!/usr/bin/env bash\n\n# Note:\n# This script should be run in the base directory of the project/service\n\n# Inputs\n#\n# This"
},
{
"path": "ch03/fake.go",
"chars": 171,
"preview": "package Hands_On_Dependency_Injection_in_Go\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) w"
},
{
"path": "ch04/01_welcome/01_bad_names.go",
"chars": 76,
"preview": "package welcome\n\ntype HouseV1 struct {\n\ta string\n\tb int\n\tt int\n\tp float64\n}\n"
},
{
"path": "ch04/01_welcome/02_improved_names.go",
"chars": 104,
"preview": "package welcome\n\ntype HouseV2 struct {\n\taddress string\n\tbedrooms int\n\ttoilets int\n\tprice float64\n}\n"
},
{
"path": "ch04/01_welcome/03_long_method.go",
"chars": 848,
"preview": "package welcome\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc longMethod(resp http.ResponseW"
},
{
"path": "ch04/01_welcome/04_long_method_test.go",
"chars": 1078,
"preview": "package welcome\n\nimport (\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-"
},
{
"path": "ch04/01_welcome/05_short_methods.go",
"chars": 1037,
"preview": "package welcome\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n)\n\nfunc shortMethods(resp http.ResponseWriter, req *ht"
},
{
"path": "ch04/03_known_issues/01_data_and_rest/get_example.go",
"chars": 418,
"preview": "//+build ignore\n\npackage data_and_rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependenc"
},
{
"path": "ch04/03_known_issues/02_config_coupling/config.go",
"chars": 236,
"preview": "package config_coupling\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/03_known_issues/"
},
{
"path": "ch04/03_known_issues/02_config_coupling/currency/currency.go",
"chars": 719,
"preview": "package currency\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\n// Currency is a custom type; used for convenience and code readab"
},
{
"path": "ch04/acme/internal/config/config.go",
"chars": 1453,
"preview": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection"
},
{
"path": "ch04/acme/internal/config/config_test.go",
"chars": 1058,
"preview": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfun"
},
{
"path": "ch04/acme/internal/logging/logging.go",
"chars": 750,
"preview": "package logging\n\nimport (\n\t\"fmt\"\n)\n\n// L is the global instance of the logger\nvar L = &LoggerStdOut{}\n\n// LoggerStdOut l"
},
{
"path": "ch04/acme/internal/modules/data/data.go",
"chars": 4304,
"preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/"
},
{
"path": "ch04/acme/internal/modules/data/data_test.go",
"chars": 566,
"preview": "package data\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc "
},
{
"path": "ch04/acme/internal/modules/exchange/converter.go",
"chars": 2853,
"preview": "package exchange\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-"
},
{
"path": "ch04/acme/internal/modules/get/get.go",
"chars": 787,
"preview": "package get\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/mod"
},
{
"path": "ch04/acme/internal/modules/get/go_test.go",
"chars": 379,
"preview": "package get\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc T"
},
{
"path": "ch04/acme/internal/modules/list/list.go",
"chars": 1017,
"preview": "package list\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/mo"
},
{
"path": "ch04/acme/internal/modules/list/list_test.go",
"chars": 307,
"preview": "package list\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc "
},
{
"path": "ch04/acme/internal/modules/register/register.go",
"chars": 2886,
"preview": "package register\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/interna"
},
{
"path": "ch04/acme/internal/modules/register/register_test.go",
"chars": 512,
"preview": "package register\n\nimport (\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/intern"
},
{
"path": "ch04/acme/internal/rest/common_test.go",
"chars": 769,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net\"\n)\n\nfunc getOpenPort() (string, error) {\n\tlistener, err := net.Listen(\"tcp\", \"12"
},
{
"path": "ch04/acme/internal/rest/get.go",
"chars": 2826,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/PacktPublishing/Hand"
},
{
"path": "ch04/acme/internal/rest/get_test.go",
"chars": 960,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
},
{
"path": "ch04/acme/internal/rest/list.go",
"chars": 1735,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in"
},
{
"path": "ch04/acme/internal/rest/list_test.go",
"chars": 1157,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
},
{
"path": "ch04/acme/internal/rest/not_found.go",
"chars": 197,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n)\n\nfunc notFoundHandler(response http.ResponseWriter, _ *http.Request) {\n\tresponse.Wr"
},
{
"path": "ch04/acme/internal/rest/not_found_test.go",
"chars": 698,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNot"
},
{
"path": "ch04/acme/internal/rest/register.go",
"chars": 2091,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
},
{
"path": "ch04/acme/internal/rest/register_test.go",
"chars": 1235,
"preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/"
},
{
"path": "ch04/acme/internal/rest/server.go",
"chars": 1402,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// New will create and initialize the server\nfunc New(a"
},
{
"path": "ch04/acme/main.go",
"chars": 388,
"preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch04/acme/internal/c"
},
{
"path": "ch04/fake.go",
"chars": 140,
"preview": "package ch04\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) will find Go code in this direct"
},
{
"path": "ch05/02_advantages/01_function.go",
"chars": 433,
"preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\nfunc SaveConfig(filename string, cfg *Config) error"
},
{
"path": "ch05/02_advantages/02_monkey_patched.go",
"chars": 667,
"preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\nfunc SaveConfigPatched(filename string, cfg "
},
{
"path": "ch05/02_advantages/03_injected_lambda.go",
"chars": 809,
"preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n)\n\nfunc SaveConfigInjected(writer fileWri"
},
{
"path": "ch05/02_advantages/04_as_object.go",
"chars": 746,
"preview": "package advantages\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n)\n\ntype ConfigSaver struct {\n\tFileWriter "
},
{
"path": "ch05/02_advantages/05_math_rand.go",
"chars": 1085,
"preview": "package advantages\n\n// A Rand is a source of random numbers.\ntype Rand struct {\n\tsrc Source\n\n\t// code removed\n}\n\n// Int "
},
{
"path": "ch05/02_advantages/06_math_rand_test.go",
"chars": 551,
"preview": "package advantages\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestInt(t *testing.T) {\n\t// monke"
},
{
"path": "ch05/03_applying/01_simple_sqlmock_test.go",
"chars": 1338,
"preview": "package applying\n\nimport (\n\t\"database/sql\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretchr/testify/a"
},
{
"path": "ch05/03_applying/02_load.go",
"chars": 2238,
"preview": "package applying\n\nimport (\n\t\"database/sql\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretch"
},
{
"path": "ch05/04_disadvantages/01_verbose.go",
"chars": 490,
"preview": "package disadvantages\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"log\"\n)\n\nfunc SaveConfig(filename string, cfg *Config) er"
},
{
"path": "ch05/04_disadvantages/02_verbose_test.go",
"chars": 620,
"preview": "package disadvantages\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSaveConfig(t *testin"
},
{
"path": "ch05/04_disadvantages/03_refactored_test.go",
"chars": 831,
"preview": "package disadvantages\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSaveConfig_refactore"
},
{
"path": "ch05/acme/internal/config/config.go",
"chars": 1453,
"preview": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection"
},
{
"path": "ch05/acme/internal/config/config_test.go",
"chars": 1058,
"preview": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfun"
},
{
"path": "ch05/acme/internal/logging/logging.go",
"chars": 750,
"preview": "package logging\n\nimport (\n\t\"fmt\"\n)\n\n// L is the global instance of the logger\nvar L = &LoggerStdOut{}\n\n// LoggerStdOut l"
},
{
"path": "ch05/acme/internal/modules/data/data.go",
"chars": 4543,
"preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/"
},
{
"path": "ch05/acme/internal/modules/data/data_test.go",
"chars": 5545,
"preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/s"
},
{
"path": "ch05/acme/internal/modules/exchange/converter.go",
"chars": 2853,
"preview": "package exchange\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-"
},
{
"path": "ch05/acme/internal/modules/get/get.go",
"chars": 873,
"preview": "package get\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/mod"
},
{
"path": "ch05/acme/internal/modules/get/go_test.go",
"chars": 1989,
"preview": "package get\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/i"
},
{
"path": "ch05/acme/internal/modules/list/list.go",
"chars": 1110,
"preview": "package list\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/mo"
},
{
"path": "ch05/acme/internal/modules/list/list_test.go",
"chars": 1949,
"preview": "package list\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/"
},
{
"path": "ch05/acme/internal/modules/register/register.go",
"chars": 2977,
"preview": "package register\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/interna"
},
{
"path": "ch05/acme/internal/modules/register/register_test.go",
"chars": 1483,
"preview": "package register\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/a"
},
{
"path": "ch05/acme/internal/rest/common_test.go",
"chars": 769,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net\"\n)\n\nfunc getOpenPort() (string, error) {\n\tlistener, err := net.Listen(\"tcp\", \"12"
},
{
"path": "ch05/acme/internal/rest/get.go",
"chars": 2826,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/PacktPublishing/Hand"
},
{
"path": "ch05/acme/internal/rest/get_test.go",
"chars": 960,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
},
{
"path": "ch05/acme/internal/rest/list.go",
"chars": 1735,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in"
},
{
"path": "ch05/acme/internal/rest/list_test.go",
"chars": 1157,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"g"
},
{
"path": "ch05/acme/internal/rest/not_found.go",
"chars": 197,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n)\n\nfunc notFoundHandler(response http.ResponseWriter, _ *http.Request) {\n\tresponse.Wr"
},
{
"path": "ch05/acme/internal/rest/not_found_test.go",
"chars": 698,
"preview": "package rest\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNot"
},
{
"path": "ch05/acme/internal/rest/register.go",
"chars": 2091,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
},
{
"path": "ch05/acme/internal/rest/register_test.go",
"chars": 1235,
"preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/"
},
{
"path": "ch05/acme/internal/rest/server.go",
"chars": 1402,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// New will create and initialize the server\nfunc New(a"
},
{
"path": "ch05/acme/main.go",
"chars": 388,
"preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch05/acme/internal/c"
},
{
"path": "ch05/fake.go",
"chars": 140,
"preview": "package ch05\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) will find Go code in this direct"
},
{
"path": "ch06/01_constructor_injection/01_welcome_email.go",
"chars": 987,
"preview": "package constructor_injection\n\nimport (\n\t\"errors\"\n)\n\nfunc NewWelcomeSender(in *Mailer) (*WelcomeSender, error) {\n\t// gua"
},
{
"path": "ch06/01_constructor_injection/01_welcome_email_test.go",
"chars": 515,
"preview": "package constructor_injection\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewWelcomeSender_h"
},
{
"path": "ch06/01_constructor_injection/02_mailer_interface.go",
"chars": 179,
"preview": "package constructor_injection\n\n// Mailer sends and receives emails\ntype MailerInterface interface {\n\tSend(to string, bod"
},
{
"path": "ch06/01_constructor_injection/03_sender_interface.go",
"chars": 508,
"preview": "package constructor_injection\n\ntype Sender interface {\n\tSend(to string, body string) error\n}\n\nfunc NewWelcomeSenderV2(in"
},
{
"path": "ch06/01_constructor_injection/05_duck_typing.go",
"chars": 313,
"preview": "package constructor_injection\n\nimport (\n\t\"fmt\"\n)\n\ntype Talker interface {\n\tSpeak() string\n\tShout() string\n}\n\ntype Dog st"
},
{
"path": "ch06/02_advantages/01_easy_to_implement.go",
"chars": 462,
"preview": "package advantages\n\n// WelcomeSender sends a Welcome email to new users\ntype WelcomeSender struct {\n\tMailer *Mailer\n}\n\nf"
},
{
"path": "ch06/02_advantages/01_easy_to_implement_example_test.go",
"chars": 274,
"preview": "package advantages_test\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages\"\n)"
},
{
"path": "ch06/02_advantages/02_easy_to_implement.go",
"chars": 444,
"preview": "package advantages\n\nfunc NewWelcomeSenderV2(mailer *Mailer) *WelcomeSenderV2 {\n\treturn &WelcomeSenderV2{\n\t\tmailer: maile"
},
{
"path": "ch06/02_advantages/02_easy_to_implement_example_test.go",
"chars": 266,
"preview": "package advantages_test\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/02_advantages\"\n)"
},
{
"path": "ch06/02_advantages/03_predictable.go",
"chars": 523,
"preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\ntype Engine interface {\n\tStart()\n\tIncreasePower()\n\tDecreasePower()\n\tStop()\n\tIs"
},
{
"path": "ch06/02_advantages/04_predictable.go",
"chars": 467,
"preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\nfunc NewCarV2(engine Engine) (*CarV2, error) {\n\tif engine == nil {\n\t\treturn ni"
},
{
"path": "ch06/02_advantages/05_encapsulation.go",
"chars": 308,
"preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\nfunc (c *CarV2) FillPetrolTank() error {\n\t// use the engine\n\tif c.engine.IsRun"
},
{
"path": "ch06/02_advantages/06_encapsulation.go",
"chars": 256,
"preview": "package advantages\n\nimport (\n\t\"errors\"\n)\n\nfunc (c *CarV2) FillPetrolTankV2(engine Engine) error {\n\t// use the engine\n\tif"
},
{
"path": "ch06/03_applying/01/01_register_handler_before.go",
"chars": 2077,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
},
{
"path": "ch06/03_applying/01/data/person.go",
"chars": 409,
"preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
},
{
"path": "ch06/03_applying/01/register/register.go",
"chars": 579,
"preview": "package register\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data\"\n)\n"
},
{
"path": "ch06/03_applying/02/01_register_handler.go",
"chars": 465,
"preview": "package rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/02/register\"\n)\n"
},
{
"path": "ch06/03_applying/02/data/person.go",
"chars": 409,
"preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
},
{
"path": "ch06/03_applying/02/register/register.go",
"chars": 579,
"preview": "package register\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/01/data\"\n)\n"
},
{
"path": "ch06/03_applying/03/data/person.go",
"chars": 409,
"preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
},
{
"path": "ch06/03_applying/03/mock_register_model_test.go",
"chars": 726,
"preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
},
{
"path": "ch06/03_applying/03/register_test.go",
"chars": 428,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestRegisterHandler_ServeHTTP(t *testing.T) {\n\tscenarios := []stru"
},
{
"path": "ch06/03_applying/04/data/person.go",
"chars": 409,
"preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
},
{
"path": "ch06/03_applying/04/mock_register_model_test.go",
"chars": 726,
"preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
},
{
"path": "ch06/03_applying/04/register.go",
"chars": 1006,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/04"
},
{
"path": "ch06/03_applying/04/register_test.go",
"chars": 1400,
"preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretch"
},
{
"path": "ch06/03_applying/05/data/person.go",
"chars": 409,
"preview": "package data\n\n// Person is the data transfer object (DTO) for this package\ntype Person struct {\n\t// ID is the unique ID "
},
{
"path": "ch06/03_applying/05/fakes.go",
"chars": 1295,
"preview": "package reset\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/0"
},
{
"path": "ch06/03_applying/05/get/getter.go",
"chars": 261,
"preview": "package get\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data\"\n)\n\n// S"
},
{
"path": "ch06/03_applying/05/list/lister.go",
"chars": 258,
"preview": "package list\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data\"\n)\n\n// "
},
{
"path": "ch06/03_applying/05/register/registerer.go",
"chars": 272,
"preview": "package register\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/data\"\n)\n"
},
{
"path": "ch06/03_applying/05/server.go",
"chars": 632,
"preview": "package reset\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/03_applying/05/get\"\n\t\"gith"
},
{
"path": "ch06/04_disadvantages/01_lots_of_changes.go",
"chars": 874,
"preview": "package disadvantages\n\n// Dealer will shuffle a deck of cards and deal them to the players\nfunc DealCards() (player1 []C"
},
{
"path": "ch06/04_disadvantages/02_overuse.go",
"chars": 1017,
"preview": "package disadvantages\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\nconst downstreamServer = \"http://www.exampl"
},
{
"path": "ch06/04_disadvantages/03_non_obvious.go",
"chars": 538,
"preview": "package disadvantages\n\nimport (\n\t\"errors\"\n)\n\n// NewClient creates and initialises the client\nfunc NewClient(service DepS"
},
{
"path": "ch06/04_disadvantages/04_non_obvious_example_test.go",
"chars": 278,
"preview": "package disadvantages_test\n\nfunc Example() {\n\n}\n\n// StubClient is a stub implementation of disadvantages.Client interfac"
},
{
"path": "ch06/04_disadvantages/05_constructors.go",
"chars": 523,
"preview": "package disadvantages\n\ntype InnerService struct {\n\tinnerDep Dependency\n}\n\nfunc NewInnerService(innerDep Dependency) *Inn"
},
{
"path": "ch06/acme/internal/config/config.go",
"chars": 1453,
"preview": "package config\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection"
},
{
"path": "ch06/acme/internal/config/config_test.go",
"chars": 1058,
"preview": "package config\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfun"
},
{
"path": "ch06/acme/internal/logging/logging.go",
"chars": 750,
"preview": "package logging\n\nimport (\n\t\"fmt\"\n)\n\n// L is the global instance of the logger\nvar L = &LoggerStdOut{}\n\n// LoggerStdOut l"
},
{
"path": "ch06/acme/internal/modules/data/data.go",
"chars": 4543,
"preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/"
},
{
"path": "ch06/acme/internal/modules/data/data_test.go",
"chars": 5545,
"preview": "package data\n\nimport (\n\t\"database/sql\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/s"
},
{
"path": "ch06/acme/internal/modules/exchange/converter.go",
"chars": 2853,
"preview": "package exchange\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-"
},
{
"path": "ch06/acme/internal/modules/get/get.go",
"chars": 873,
"preview": "package get\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/mod"
},
{
"path": "ch06/acme/internal/modules/get/go_test.go",
"chars": 1989,
"preview": "package get\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/i"
},
{
"path": "ch06/acme/internal/modules/list/list.go",
"chars": 1110,
"preview": "package list\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/mo"
},
{
"path": "ch06/acme/internal/modules/list/list_test.go",
"chars": 1949,
"preview": "package list\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/"
},
{
"path": "ch06/acme/internal/modules/register/register.go",
"chars": 2977,
"preview": "package register\n\nimport (\n\t\"errors\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/interna"
},
{
"path": "ch06/acme/internal/modules/register/register_test.go",
"chars": 1483,
"preview": "package register\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/a"
},
{
"path": "ch06/acme/internal/rest/get.go",
"chars": 3120,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/PacktPublishing/Hand"
},
{
"path": "ch06/acme/internal/rest/get_test.go",
"chars": 4043,
"preview": "package rest\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/"
},
{
"path": "ch06/acme/internal/rest/list.go",
"chars": 1967,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in"
},
{
"path": "ch06/acme/internal/rest/list_test.go",
"chars": 3057,
"preview": "package rest\n\nimport (\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/PacktPublishing/"
},
{
"path": "ch06/acme/internal/rest/mock_get_model_test.go",
"chars": 752,
"preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
},
{
"path": "ch06/acme/internal/rest/mock_list_model_test.go",
"chars": 743,
"preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
},
{
"path": "ch06/acme/internal/rest/mock_register_model_test.go",
"chars": 733,
"preview": "// Code generated by mockery v1.0.0\n\n// @generated\n\npackage rest\n\nimport (\n\t\"github.com/PacktPublishing/Hands-On-Depende"
},
{
"path": "ch06/acme/internal/rest/not_found.go",
"chars": 197,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n)\n\nfunc notFoundHandler(response http.ResponseWriter, _ *http.Request) {\n\tresponse.Wr"
},
{
"path": "ch06/acme/internal/rest/not_found_test.go",
"chars": 374,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestN"
},
{
"path": "ch06/acme/internal/rest/register.go",
"chars": 2373,
"preview": "package rest\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-i"
},
{
"path": "ch06/acme/internal/rest/register_test.go",
"chars": 3148,
"preview": "package rest\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.c"
},
{
"path": "ch06/acme/internal/rest/server.go",
"chars": 1511,
"preview": "package rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\n// New will create and initialize the server\nfunc New(a"
},
{
"path": "ch06/acme/main.go",
"chars": 841,
"preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/PacktPublishing/Hands-On-Dependency-Injection-in-Go/ch06/acme/internal/c"
},
{
"path": "ch06/fake.go",
"chars": 140,
"preview": "package ch06\n\nfunc init() {\n\t// This file is included so that Go tools (like `go list`) will find Go code in this direct"
},
{
"path": "ch06/pcov-html",
"chars": 466,
"preview": "#!/usr/bin/env bash\n\nif [ \"$1\" == \"\" ]; then\n\techo \"No input file. Usage: pcov-html ./your-package-dir/\"\n\texit 1\nfi\n\n# I"
},
{
"path": "ch07/01_method_injection/01_fprint.go",
"chars": 109,
"preview": "package method_injection\n\nimport (\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc ExampleA() {\n\tfmt.Fprint(os.Stdout, \"Hello World\")\n}\n"
},
{
"path": "ch07/01_method_injection/02_http_request.go",
"chars": 304,
"preview": "package method_injection\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc ExampleB() {\n\t// added to make the compiler happy"
},
{
"path": "ch07/01_method_injection/03_fprint.go",
"chars": 271,
"preview": "package method_injection\n\nimport (\n\t\"io\"\n)\n\n// Fprint formats using the default formats for its operands and writes to w"
},
{
"path": "ch07/01_method_injection/04_http_request.go",
"chars": 705,
"preview": "package method_injection\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/url\"\n)\n\nfunc NewRequest(method, url string, body"
},
{
"path": "ch07/01_method_injection/05_timestamp_writer_v1.go",
"chars": 309,
"preview": "package method_injection\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// TimeStampWriterV1 will output the supplied message to writ"
},
{
"path": "ch07/01_method_injection/06_timestamp_writer_v2.go",
"chars": 406,
"preview": "package method_injection\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// TimeStampWriterV2 will output the supplied messa"
},
{
"path": "ch07/01_method_injection/07_timestamp_writer_v3.go",
"chars": 389,
"preview": "package method_injection\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n)\n\n// TimeStampWriterV3 will output the supplied message t"
}
]
// ... and 456 more files (download for full content)
About this extraction
This page contains the full source code of the PacktPublishing/Hands-On-Dependency-Injection-in-Go GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 656 files (1.5 MB), approximately 436.7k tokens, and a symbol index with 3579 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.