Skip to content

Commit 28f8983

Browse files
committed
code refactor
1 parent 11e53ea commit 28f8983

File tree

7 files changed

+340
-208
lines changed

7 files changed

+340
-208
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,7 @@
11
defmodule ElixirProvider do
2-
@behaviour OpenFeature.Provider
3-
4-
alias OpenFeature.ResolutionDetails
5-
alias ElixirProvider.GoFeatureFlagOptions
6-
alias ElixirProvider.HttpClient
7-
alias ElixirProvider.DataCollectorHook
8-
alias ElixirProvider.CacheController
9-
alias ElixirProvider.ResponseFlagEvaluation
10-
alias ElixirProvider.GoFWebSocketClient
11-
alias ElixirProvider.RequestFlagEvaluation
12-
alias ElixirProvider.ContextTransformer
13-
alias ElixirProvider.GofEvaluationContext
14-
152
@moduledoc """
16-
The GO Feature Flag provider for OpenFeature, managing HTTP requests, caching, and flag evaluation.
17-
"""
18-
19-
defstruct [
20-
:options,
21-
:http_client,
22-
:data_collector_hook,
23-
:ws,
24-
:domain
25-
]
26-
27-
@type t :: %__MODULE__{
28-
options: GoFeatureFlagOptions.t(),
29-
http_client: HttpClient.t(),
30-
data_collector_hook: DataCollectorHook.t() | nil,
31-
ws: GoFWebSocketClient.t(),
32-
domain: String.t()
33-
}
34-
35-
@impl true
36-
def initialize(%__MODULE__{} = provider, domain, _context) do
37-
{:ok, http_client} = HttpClient.start_http_connection(provider.options)
38-
CacheController.start_link(provider.options)
39-
{:ok, data_collector_hook} = DataCollectorHook.start_link(provider.options, http_client)
40-
{:ok, ws} = GoFWebSocketClient.start_link(provider.options.endpoint)
41-
42-
updated_provider = %__MODULE__{
43-
provider
44-
| domain: domain,
45-
http_client: http_client,
46-
data_collector_hook: data_collector_hook,
47-
ws: ws
48-
}
49-
50-
{:ok, updated_provider}
51-
end
52-
53-
@impl true
54-
def shutdown(%__MODULE__{ws: ws} = provider) do
55-
Process.exit(ws, :normal)
56-
CacheController.clear()
57-
if provider.data_collector_hook, do: DataCollectorHook.shutdown(provider.data_collector_hook)
58-
:ok
59-
end
60-
61-
@impl true
62-
def resolve_boolean_value(provider, key, default, context) do
63-
generic_resolve(provider, :boolean, key, default, context)
64-
end
65-
66-
@impl true
67-
def resolve_string_value(provider, key, default, context) do
68-
generic_resolve(provider, :string, key, default, context)
69-
end
70-
71-
@impl true
72-
def resolve_number_value(provider, key, default, context) do
73-
generic_resolve(provider, :number, key, default, context)
74-
end
75-
76-
@impl true
77-
def resolve_map_value(provider, key, default, context) do
78-
generic_resolve(provider, :map, key, default, context)
79-
end
80-
81-
defp generic_resolve(provider, type, flag_key, default_value, context) do
82-
{:ok, goff_context} = ContextTransformer.transform_context(context)
83-
goff_request = %RequestFlagEvaluation{user: goff_context, default_value: default_value}
84-
eval_context_hash = GofEvaluationContext.hash(goff_context)
85-
86-
response_body =
87-
case CacheController.get(flag_key, eval_context_hash) do
88-
{:ok, cached_response} ->
89-
cached_response
90-
91-
:miss ->
92-
# Fetch from HTTP if cache miss
93-
case HttpClient.post(provider.http_client, "/v1/feature/#{flag_key}/eval", goff_request) do
94-
{:ok, response} -> handle_response(flag_key, eval_context_hash, response)
95-
{:error, reason} -> {:error, {:unexpected_error, reason}}
96-
end
97-
end
98-
99-
handle_flag_resolution(response_body, type, flag_key, default_value)
100-
end
101-
102-
defp handle_response(flag_key, eval_context_hash, response) do
103-
# Build the flag evaluation struct directly from the response map
104-
flag_eval = ResponseFlagEvaluation.decode(response)
105-
106-
# Cache the response if it's marked as cacheable
107-
if flag_eval.cacheable do
108-
CacheController.set(flag_key, eval_context_hash, response)
109-
end
110-
111-
{:ok, flag_eval}
112-
end
113-
114-
defp handle_flag_resolution(response, type, flag_key, _default_value) do
115-
case response do
116-
{:ok, %ResponseFlagEvaluation{value: value, reason: reason}} ->
117-
case {type, value} do
118-
{:boolean, val} when is_boolean(val) ->
119-
{:ok, %ResolutionDetails{value: val, reason: reason}}
120-
121-
{:string, val} when is_binary(val) ->
122-
{:ok, %ResolutionDetails{value: val, reason: reason}}
123-
124-
{:number, val} when is_number(val) ->
125-
{:ok, %ResolutionDetails{value: val, reason: reason}}
126-
127-
{:map, val} when is_map(val) ->
128-
{:ok, %ResolutionDetails{value: val, reason: reason}}
129-
130-
_ ->
131-
{:error, {:variant_not_found, "Expected #{type} but got #{inspect(value)} for flag #{flag_key}"}}
132-
end
133-
134-
_ ->
135-
{:error, {:flag_not_found, "Flag #{flag_key} not found"}}
136-
end
137-
end
3+
`ElixirProvider` is a feature flag manager for controlling feature availability in Go applications.
1384
5+
It allows toggling features dynamically based on configurations from sources like databases and APIs, enabling flexible, real-time control over application behavior.
6+
"""
1397
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
defmodule ElixirProvider.Application do
2+
# See https://hexdocs.pm/elixir/Application.html
3+
# for more information on OTP Applications
4+
@moduledoc false
5+
6+
use Application
7+
8+
@impl true
9+
def start(_type, _args) do
10+
children = [
11+
ExSd.ServerSupervisor
12+
]
13+
14+
# See https://hexdocs.pm/elixir/Supervisor.html
15+
# for other strategies and supported options
16+
opts = [strategy: :one_for_one, name: ExSd.Supervisor]
17+
Supervisor.start_link(children, opts)
18+
end
19+
20+
end

openfeature/providers/elixir-provider/lib/provider/cache_controller.ex

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ defmodule ElixirProvider.CacheController do
66
use GenServer
77
@flag_table :flag_cache
88

9-
@spec start_link(Keyword.t()) :: GenServer.on_start()
10-
def start_link(opts) do
11-
name = Keyword.get(opts, :name, __MODULE__)
12-
GenServer.start_link(__MODULE__, :ok, name: name)
9+
@spec start_link() :: GenServer.on_start()
10+
def start_link() do
11+
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
1312
end
1413

1514
def get(flag_key, evaluation_hash) do
@@ -27,6 +26,7 @@ defmodule ElixirProvider.CacheController do
2726
end
2827

2928
def clear do
29+
GenServer.stop(__MODULE__)
3030
:ets.delete_all_objects(@flag_table)
3131
:ets.insert(@flag_table, {:context, %{}})
3232
:ok

openfeature/providers/elixir-provider/lib/provider/data_collector_hook.ex

+21-18
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
defmodule ElixirProvider.DataCollectorHook do
2+
23
use GenServer
34
require Logger
45

56
alias ElixirProvider.HttpClient
6-
# alias OpenFeature.{EvaluationDetails, HookContext}
77
alias ElixirProvider.{FeatureEvent, RequestDataCollector}
88

99
@default_targeting_key "undefined-targetingKey"
@@ -25,11 +25,11 @@ defmodule ElixirProvider.DataCollectorHook do
2525
}
2626

2727
# Starts the GenServer and initializes with options
28-
def start_link(options, http_client) do
29-
GenServer.start_link(__MODULE__, {options, http_client: http_client}, name: __MODULE__)
28+
def start_link() do
29+
GenServer.start_link(__MODULE__, [], name: __MODULE__)
3030
end
3131

32-
def shutdown(state) do
32+
def stop(state) do
3333
GenServer.stop(__MODULE__)
3434
collect_data(state.data_flush_interval)
3535
%__MODULE__{
@@ -41,27 +41,31 @@ defmodule ElixirProvider.DataCollectorHook do
4141
}
4242
end
4343

44-
# Initializes GenServer state and schedules the first flush
45-
def init(args) do
44+
@impl true
45+
def init([]) do
46+
{:ok, %__MODULE__{}}
47+
end
48+
49+
# Initializes the state with the provided options
50+
def start(options, http_client) do
4651
state = %__MODULE__{
47-
http_client: args.http_client,
48-
data_collector_endpoint: args.options.endpoint,
49-
disable_data_collection: args.options.disable_data_collection || false,
50-
data_flush_interval: args.options.data_flush_interval || 60_000,
52+
http_client: http_client,
53+
data_collector_endpoint: options.endpoint,
54+
disable_data_collection: options.disable_data_collection || false,
55+
data_flush_interval: options.data_flush_interval || 60_000,
5156
event_queue: []
5257
}
5358

5459
schedule_collect_data(state.data_flush_interval)
5560
{:ok, state}
5661
end
5762

58-
# Schedule periodic flush based on the interval
63+
# Schedule periodic data collection based on the interval
5964
defp schedule_collect_data(interval) do
6065
Process.send_after(self(), :collect_data, interval)
6166
end
6267

6368
### Hook Implementations
64-
6569
def after_hook(hook, hook_context, flag_evaluation_details, _hints) do
6670
if hook.disable_data_collection or flag_evaluation_details.reason != :CACHED do
6771
:ok
@@ -76,12 +80,11 @@ defmodule ElixirProvider.DataCollectorHook do
7680
user_key: Map.get(hook_context.evaluation_context, "targeting_key") || @default_targeting_key
7781
}
7882

79-
# Send event to GenServer process to append to queue
8083
GenServer.cast(__MODULE__, {:add_event, feature_event})
8184
end
8285
end
8386

84-
def error_hook(hook, hook_context, _hints) do
87+
def error(hook, hook_context, _hints) do
8588
if hook.disable_data_collection do
8689
:ok
8790
else
@@ -95,24 +98,24 @@ defmodule ElixirProvider.DataCollectorHook do
9598
user_key: Map.get(hook_context.context, "targeting_key") || @default_targeting_key
9699
}
97100

98-
# Send error event to GenServer process to append to queue
99101
GenServer.call(__MODULE__, {:add_event, feature_event})
100102
end
101103
end
102104

103105
### GenServer Callbacks
104-
def handle_call({:add_event, feature_event}, state) do
105-
{:noreply, %{state | event_queue: [feature_event | state.event_queue]}}
106+
@impl true
107+
def handle_call({:add_event, feature_event}, _from, state) do
108+
{:reply, :ok, %{state | event_queue: [feature_event | state.event_queue]}}
106109
end
107110

108111
# Handle the periodic flush
112+
@impl true
109113
def handle_info(:collect_data, state) do
110114
case collect_data(state) do
111115
:ok -> Logger.info("Data collected and sent successfully.")
112116
{:error, reason} -> Logger.error("Failed to send data: #{inspect(reason)}")
113117
end
114118

115-
# Schedule the next flush
116119
schedule_collect_data(state.data_flush_interval)
117120
{:noreply, %{state | event_queue: []}}
118121
end

0 commit comments

Comments
 (0)