-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is it possible to use return value in key-generation along with the passed parameters ? #132
Comments
Hey 👋 ! I'm not very sure if I understand, but will try to reply. I see a couple of things here. About your question, no, it is not possible to use the returned value in the key generator because:
On the other hand, I see you are not using the key generation in the right way, maybe I'm missing something, but the idea is to use the @decorate cacheable(
cache: Cache,
key_generator: __MODULE__,
opts: [ttl: :timer.seconds(30)]
)
def get_entity(attrs) do
Entity.get_entity(attrs) # => %Entity{...}
end
@impl true
def generate(mod, fun, args) do
# your key generation logic ...
end Let me know, stay tuned! Thanks! |
Thanks a lot @cabol for the reply. I've a module( I understand why it's not possible to generate key from returned value. I'll try to add some defmodule Cache do
...
def generate(mod, fun, args) do
caching_module = Map.get(args, :caching_module, nil)
case caching_module do
nil -> {:error, "don't save the cache"}
_ ->{ caching_module , :erlang.phash2({caching_module, fun, args})}
end
end
end
# Generic CRUD module
defmodule Entity do
@decorate cacheable(
cache: Cache,
opts: [ttl: :timer.seconds(30)]
)
def get_entity(attrs) do
Entity.get_entity(attrs) # => %Entity{...}
end
end
# Context with many modules
defmodule Accounts do
def get_user(attrs) do # There are 35 more such entities like User.
attrs = Map.merge(attrs, %{cache_module: User})
end
end Is it possible to save or not-save value/result based on the Cache that's generated by KeyGenerator? Is is also possible to have a function for @decorate cacheable(
cache: Cache,
keys: CacheEviction.keys(args)
)
def delete_user() do
end
defmodule CacheEvicition do
def keys(attrs) do
# somehow get all the keys with pattern {User, ...} # where attrs.caching_module == User
end
end |
Let me first try to answer your inquiries, and then I will give you an example of how you could achieve what you want.
You shouldn't use the key generator result to determine if the value/result has to be cached, the generator has a very simple/single responsibility which is to provide the key under which the result will be cached, that's all. If you have very custom logic, I think you have to use the cache API instead of the annotations. However, I'd suggest you review the documentation examples as well as the Ecto example, perhaps can give you a better idea about how to use the annotations in your contexts.
No, it is not possible. Normally, when you use a cache is because your data has some kind of unique key or keys, like an Now, regarding your scenario, you say you have a kind of generic context with the CRUD functions for all your schemas, consider passing in the defmodule MyApp.Entity do
use Nebulex.Caching
alias MyApp.Cache
@decorate cacheable(
cache: Cache,
key: {schema, id},
opts: [ttl: :timer.seconds(30)]
)
def get_entity(%{schema: schema, id: id} = attrs) do
Entity.get_entity(attrs) # => %Entity{...}
end
end If you have different key fields per entity, you can use pattern-matching, like so: defmodule MyApp.Entity do
use Nebulex.Caching
alias MyApp.Cache
@decorate cacheable(
cache: Cache,
key_generator: __MODULE__,
opts: [ttl: :timer.seconds(30)]
)
def get_entity(attrs) do
# => %Entity{...}
Entity.get_entity(attrs)
end
@decorate cache_evict(
cache: Cache,
key_generator: __MODULE__
)
def delete_entity(attrs) do
# => %Entity{...}
Entity.delete_entity(attrs)
end
# Assuming the attrs is the entity/schema
@impl true
def generate(__MODULE__, fun, [attrs]) when fun in [:get_entity, :delete_entity] do
case attrs do
%User{username: username} -> {User, username}
%Company{id: id} -> {Company, id}
%Country{code: code} -> {Country, code}
# more ...
end
end
end
Please let me know if something like that is what you are trying to achieve, maybe I can help you more, otherwise, try to give me more details, and also check the examples in the doc and the Ecto example I sent you the link. I stay tuned! |
Thanks a lot @cabol for your reply. def get_entity(query, _attrs = %{id: id}) when is_binary(id) do
Repo.get(query, id)
end
def get_entity(query, attrs) when is_map(attrs) do
# ... Find Logic ...
end
def get_entity(query, %{}) when is_atom(query), do: nil
def get_entity(%{} = _attrs), do: nil And my %{
filters: [%{field: :name, query_operation: :contains, search_term: "ind"}],
limit: 40,
order_by: [%{field: :name, sort_order: :asc}],
pagination: %{limit: 30}
} Since, they are quite dynamic in nature too, it would be hard to cache them. I was actually looking for a way to put caching on For Cache based on id, name or single unique parameter has been sorted thanks to your awesome docs 😃 |
One more question: defmodule Auth do
@decorate cacheable(
cache: Cache,
key: {AuthRule, attrs.name},
opts: [ttl: :timer.seconds(30)]
)
def get_auth_rule(%{name: name} = attrs) do
Repo.get_by(AuthRule, name: name)
end
def get_auth_rule(%{id: id} = attrs) do
Repo.get(id)
end
def get_auth_rule(attrs), do: Crud.get_entity(AuthRule, attrs)
end When I run the function, I get the following error: iex(1)> Auth.get_auth_rule(%{name: "hello"})
%AuthRule{..., name: "hello", ...}
iex(2)> Auth.get_auth_rule(%{id: 1})
** (KeyError) key :name not found in: %{id: 1 } Am I doing something wrong ? @decorate cacheable(
cache: Cache,
key: {AuthRule, attrs.name},
opts: [ttl: :timer.seconds(30)]
)
def get_auth_rule(%{name: name} = attrs) do
Repo.get_by(AuthRule, name: name)
end One last question(🙈): Is there a way to get all the available keys in the cache ? |
Hey, no worries, let me answer your question.
Right, the thing is the annotation covers all function clauses, but nothing it cannot be handled, you can do something like this: defmodule Auth do
def get_auth_rule(%{name: name}) do
get_auth_rule_by_name(name)
end
def get_auth_rule(%{id: id}) do
Repo.get(id)
end
def get_auth_rule(attrs), do: Crud.get_entity(AuthRule, attrs)
@decorate cacheable(
cache: Cache,
key: {AuthRule, name},
opts: [ttl: :timer.seconds(30)]
)
defp get_auth_rule_by_name(name) do
Repo.get_by(AuthRule, name: name)
end
end As you can see, for those cases the trick is to create a private function and use the
I think that is because you are not using the
Yes, it is, it should be like
I'd like to start by saying, not all the data is or has to be suitable to cache, and part of the work is actually identifying the data really worth to cache because it will improve the performance by reducing database contention significantly, otherwise, I'd reconsider using a cache. For instance, the def get_entity(query, %{id: id}) when is_binary(id) do
get_entity_by_id(id)
end
# Maybe an identified common query worth to be cached
# REMEMBER you can also use guards and validate more operation, etc.
def get_entity(query, %{
filters: [%{field: :name, query_operation: :contains, search_term: "ind"}],
limit: 40,
order_by: [%{field: :name, sort_order: :asc}],
pagination: %{limit: 30}
} = attrs) do
get_entity_by_query(attrs)
end
def get_entity(query, _attrs = %{id: id}) when is_binary(id) do
get_entity_by_id(id)
end
def get_entity(query, attrs) when is_map(attrs) do
# ... Find Logic ...
end
# BE CAREFUL, the %{} is not matching an empty map, you have to use the guard map_size(map)
def get_entity(query, %{}) when is_atom(query), do: nil
@decorate cacheable(
cache: Cache,
key: id,
opts: [ttl: :timer.seconds(30)]
)
defp get_entity_by_id(id) do
Repo.get(query, id)
end
@decorate cacheable(
cache: Cache,
key: attrs,
opts: [ttl: :timer.seconds(30)]
)
defp get_entity_by_query(attrs) do
# ... Find Logic ...
end In the meantime, if you are not sure, just avoid the cache for those "search" or "dynamic queries" cases. Let me know if that helps, stay tuned, thanks! |
Thanks a ton @cabol for your valuable feedback 🙌🏽 I'll try to measure and finetune my operations 🎸 @decorate cacheable(
cache: Cache,
key: {AuthRule, attrs.name},
opts: [ttl: :timer.seconds(30)]
)
def get_auth_rule(%{name: name} = attrs) do
Repo.get_by(AuthRule, name: name)
end By this code i meant that if we have just one function like above where name variable is coming from pattern-matching, |
Can we use the returned
%Entity{}
in KeyGeneration along withattrs
?The text was updated successfully, but these errors were encountered: