aluno: Luty Rodrigues Ribeiro
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 17/12/2008 (12)
Introdução
Este laboratório tem como objetivo final desenvolver o servidor de um "Bolão Virtual", que consiste de um sistema de apostas (no nosso caso, em partidas de futebol). A idéia é que este servidor possa ser integrado ao lab6, que é o cliente do Bolão Virtual. Ao longo do desenvolvimento deste laboratório, vão sendo abordados vários tópicos vistos nas últimas semanas de aula.
Desenvolvimento
A primeira parte foi melhorar os decorators de Thread Safety e de Persistência da classe MatchList, que, conforme foi passado nas instruções, tinha problemas com os métodos make_bet e finalize da classe Match, que quebravam os requisitos de Thread Safety e de Persistência. Essa melhoria foi implementada criando as classes indicadas nas instruções, da maneira como foi descrito. Abaixo, apresentamos a implementação das 4 classes adicionais propostas:
require 'monitor' require 'decorators/decorator' module Decorators class MatchThreadSafeDecorator < Decorator include MonitorMixin def make_bet(score, user) synchronize do @decorated.make_bet(score, user) end end def finalize(final_score) synchronize do @decorated.finalize(final_score) end end end end
require 'monitor' require 'decorators/decorator' require 'decorators/list_thread_safe_decorator' module Decorators class MatchListThreadSafeDecorator < ListThreadSafeDecorator include MonitorMixin def initialize(decorated) old_match_decorator = decorated.item_decorator decorated.item_decorator = lambda do |match| MatchThreadSafeDecorator.new( old_match_decorator.call(match) ) end super end end end
require 'yaml' require 'decorators/decorator' module Decorators class MatchPersistanceDecorator < Decorator attr_accessor :my_match_list_persistente def make_bet(score, user) return_object = @decorated.make_bet(score, user) @my_match_list_persistente.save return_object end def finalize(final_score) return_object = @decorated.finalize(final_score) @my_match_list_persistente.save return_object end end end
require 'yaml' require 'decorators/decorator' require 'decorators/list_persistance_decorator' module Decorators class MatchListPersistanceDecorator < ListPersistanceDecorator def initialize(decorated, filename) old_match_decorator = decorated.item_decorator decorated.item_decorator = lambda do |match| MatchPersistanceDecorator.new( old_match_decorator.call(match) ) end super(decorated, filename) end def add(*args) obj = @decorated.add(*args) #obj é um match decorado obj.my_match_list_persistente = self save obj end def list_current list = @decorated.reject { |m| m.finalized? } list.each { |obj| obj.my_match_list_persistente = self } list end def list_finalized list = @decorated.find_all { |m| m.finalized? } list.each { |obj| obj.my_match_list_persistente = self } list end def find_by_name(name) obj = @decorated.find{ |match| match.name == name } obj.my_match_list_persistente = self obj end end end
O passo seguinte foi a criação das classes MatchListBuilder e UserListBuilder, que basicamente criam e decoram suas List's respectivas. Seguindo as intruções do lab e das aulas, temos a seguir as duas classes Builder:
require 'match_list' require 'decorators/decorator' require 'decorators/match_list_persistance_decorator' require 'decorators/match_list_logger_decorator' require 'decorators/match_list_thread_safe_decorator' class MatchListBuilder attr_writer :filename def initialize @match_list = MatchList.new @added_persistance = false end def match_list @match_list.load_list if @added_persistance @match_list end def add_persistance @match_list = MatchListPersistanceDecorator.new(@match_list, @filename) @added_persistance = true end def add_logging @match_list = MatchListLoggerDecorator.new(@match_list) end def add_thread_safety @match_list = MatchListThreadSafeDecorator.new(@match_list) end def add_all add_persistance add_logging add_thread_safety end end
require 'user_list' require 'decorators/decorator' require 'decorators/list_persistance_decorator' require 'decorators/user_list_logger_decorator' require 'decorators/list_thread_safe_decorator' class UserListBuilder attr_writer :filename def initialize @user_list = UserList.new @added_persistance = false end def user_list @user_list.load_list if @added_persistance @user_list end def add_persistance @user_list = ListPersistanceDecorator.new(@user_list, @filename) @added_persistance = true end def add_logging @user_list = UserListLoggerDecorator.new(@user_list) end def add_thread_safety @user_list = ListThreadSafeDecorator.new(@user_list) end def add_all add_persistance add_logging add_thread_safety end end
Abaixo, rodamos o teste acrescentando as classes acima e modificando o integration_test.rb para utilizar MatchListThreadSafeDecorator no lugar de ListThreadSafeDecorador:
Criados os decorators e builders, passamos agora à parte deste lab feita em dupla, que é a elaboração do método main, que deverá utilizar os builder criados para decorar as MatchLists e UserLists criadas, para que estas possam ter suas informações persistidas, sejam Thread Safety e mandem informações para um arquivo de log. Para armazenar essas informações, será utilizado o arquivo yml. Desta forma, criamos uma classe RequestReader qie seria responsável por ler do console (input e output padrões, que serão os utilizados nesta altura do lab) a entrada já no formato yaml (como exemplificado nas intrções deste lab). Esta entrada será constituída de uma operação (cujos nomes são definidos pela dupla) e de parâmetros, cuja quantidade varia de acordo com a operação escolhida. Após ler essa entrada corretamente, o objeto da classe RequestReader terá formado um hash (batizado de hash requisicao no exemplo das instruções desse lab) e, a partir dele, será possível realizar uma das operações pré-definidas, gerar a saída no console para o usuário (em formato yaml) e armazenar as possíveis modificações num arquivo .yml (para nossa aplicação, serão os arquivos match_list.yml e user_list.yml).
Seguem abaixo as operações definidas, bem como os parâmetros requisitados:
Criar Usuario
-> operação create_user
-> parãmetros nome do usuário, senha
Autenticar Usuario(Deve ser feita antes de qualquer uma das operações a seguir. Se for mal sucedida, encera a aplicação. Bem sucedida, define qual o usuário para o qual as operações seguintes serão realizadas)
-> operação login_user
-> parâmetros nome do usuário, senha
Listar partidas atuais (retornando uma lista de nomes de partidas com algumas informações básicas a respeito de cada uma).
-> operação list_current_matches
Listar partidas encerradas (semelhante a anterior)
-> operação list_finalized_matches
Informações detalhadas de uma partida (apostadores, ganhadores)
-> operação show_match
-> parâmetros nome da partida
Listar informações das minhas apostas (vencedoras, em andamento, finalizadas)
-> operação show_my_bets
Fazer aposta em uma partida
-> operação make_bet
-> parâmetros nome da partida, placar
Encerrar a conexão
-> operação close_connection
OBS: Por algum motivo (que não recordo agora) o método do arquivo main.rb chama-se main_method em vez de main. Mas isso não afeta o funcionamento do programa.
Tendo definido o protocolo yaml e o nome das operações, pudemos prosseguir com a implementação da classe RequestReader, para que pudesse ler as operações acima descritas e realizá-las adequadamente. Devido à proximidade das férias, não foi possível para a nossa dupla terminar esta classe antes de viajarmos, mas conseguimos deixar um esboço bem encaminhado, para que pudéssemos completar os detalhes mais adiante. Criamos também novas classes printer, que vimos que seriam úteis, tendo como base os printers dados como exemplo.
Tendo comlpetado essa parte, a última parte é fazer o arquivo server.rb, que segue abaixo (apenas com algumas alterações, visto que o programa começará a rodar a partir dele, pelo comando "ruby server.rb"):
require 'socket' require 'initialize' require 'user_list_builder' require 'main' require 'logger' $logger = Logger.new("bolao.log") $logger.level = Logger::INFO class String def to_score self.strip.gsub(/\s+/, '').downcase end end ml_builder = MatchListBuilder.new ml_builder.filename = "match_list.yml" ml_builder.add_all $match_list = ml_builder.match_list #~ $match_list.add("atletico x santos", Time.now + 50000) #~ $match_list.add("palmeiras x flamengo", Time.now + 300000) #~ $match_list.add("santos x palmeiras", Time.now + 700000) #~ $match_list.add("santos x cruzeiro", Time.now - 200000) #~ match = $match_list.add("flamengo x cruzeiro", Time.now - 500000) #~ match.finalize("3x 2") #~ $match_list.find_by_name("palmeiras x flamengo").finalize("2 x 4") puts "match_list construida com sucesso" ul_builder = UserListBuilder.new ul_builder.filename = "user_list.yml" ul_builder.add_all $user_list = ul_builder.user_list puts "listas criadas" server = TCPServer.new(4344) # 4344 � a porta de conex�o de rede usada # utilize de prefer�ncia um n�mero acima de # 2000 (e menor que 64000). while ( io_socket = server.accept ) #aguarda at� que conex�o seja recebida, retornando um TCPSocket com a conex�o. # dispare uma nova thread e utilize io_socket para efetuar a entrada e sa�da do m�todo main, de dentro desta thread Thread.new do puts "chamou main_method" main_method(io_socket, io_socket) end #io_socket deve ser utilizado tanto para sa�da quanto para entrada, portanto � passado como par�metro in e par�metro out de main. end
O código acima exibirá algumas mensagens que não são necessárias (e não influenciam no funcionamento do projeto), mas que ajudam a ter certeza de que as requisições estão sendo atendidas devidamente (foram bastante úteis na hora de testar e descobrir os erros que apareceram).
Por algum motivo (que não consegui identificar), o programa não estava recohecendo a variável $logger global, definida em initialize.rb. O require 'initialize' colocado em server.rb parave não ter adiantado, por isso, reescrevi o trecho de código que estava em initialize.rb (e ele passou a funcionar depois disso). Preferi fazer isso, para poder seguir adiante com os testes e terminar o lab5, pois acho que o motivo não deve ser nada muito complexo, se comparado ao restante do trabalho. A seguir, temos uma sequência de testes, mostrando as requisições feitas utilizando o protocolo adotado, e a resposta a essa requisição, tanto no putty (programa para conexão telnet), como no prompt de comando, através das mensagens auxiliares acrescentadas:
Rodando o servidor:
Conectando no servidor, pelo port dado:
Fazendo uma requisição de login (para um usuário já criado). O protocolo é digitado na tela gerada pelo putty, e, após digitarmos FIM e darmos enter, vemos a resposta imediata no prompt de comando, indicando que a operação foi bem sucedida. A linha no prompt que mostra main_method indica que a nova Thread foi criada para atender à nova conexão:
Requisição para mostrar a partida palmeiras x flamengo:
Requisição para mostar as partidas em andamento. Ao realizar esses testes, escrevi por engano show_current_matches no lugar da operação list_current_matches. A resposta no prompt de comando foi a linha indicando operação nao reconhecida. Em seguida, foi feita a requisição com o nome correto da operação:
Fazendo uma segunda conexão com o servidor:
As demais operações são análogas as apresentadas acima.
Foram acrescentados ainda as operações show_match_holders e show_match_winners, e o printer yaml_holders_printer. Eles não foram planejados para este lab5, mas percebi que seriam úteis para o lab6, então, implementei eles, inspirado nas operações e printers já feitos. Abaixo, um link com o projeto compactado:
Conclusão
Este lab explorou diversos conceitos, padrões de projeto, dentre outros tópicos vistos em aula. Devido a não trivialidade em mostrar alguns desses tópicos funcionando, e pela quantidade de tópicos desse tipo contidas neste lab, o lab5 ficou meio extenso. Sem contar os erros que vão normalmente vão aparecendo, e o tempo que se pode levar, dependendo do erro. Acho que pela falta de um domínio maior da linguagem, foi perdido muito tempo na procura de erros que não eram tão complexos (como problemas com os requires).
Isso pode ser resultado do fato de as aulas de Ruby, por terem ficado nas últimas semanas, terem sido um pouco aceleradas, de modo que foi visto muita coisa e não foi possível ter uma absorção muito boa.
Por outro lado, o aprendizado com este lab foi bastante válido, mostrando o uso de protocolos, persistência, thread safety, multi-thread, pois, depois de completar o lab, temos esses conceitos funcionando na prática, numa aplicação que poderia, talvez com algumas melhorias, poderia estar sendo utilizada não apenas para fins acadêmicos. Então, foi uma experiência bastante válida, nesse sentido.





